/*
 * FreeRTOS+FAT V2.3.3
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

/**
 *	@file		ff_file.c
 *	@ingroup	FILEIO
 *
 *	@defgroup	FILEIO FILE I/O Access
 *	@brief		Provides an interface to allow File I/O on a mounted IOMAN.
 *
 *	Provides file-system interfaces for the FAT file-system.
 **/

#include "ff_headers.h"

#if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
    #include <wchar.h>
#endif

static FF_Error_t FF_Truncate( FF_FILE * pxFile,
                               BaseType_t bClosing );

static uint32_t FF_ReadPartial( FF_FILE * pxFile,
                                uint32_t ulItemLBA,
                                uint32_t ulRelBlockPos,
                                uint32_t ulCount,
                                uint8_t * pucBuffer,
                                FF_Error_t * pxError );

static uint32_t FF_WritePartial( FF_FILE * pxFile,
                                 uint32_t ulItemLBA,
                                 uint32_t ulRelBlockPos,
                                 uint32_t ulCount,
                                 const uint8_t * pucBuffer,
                                 FF_Error_t * pxError );

static uint32_t FF_SetCluster( FF_FILE * pxFile,
                               FF_Error_t * pxError );
static uint32_t FF_FileLBA( FF_FILE * pxFile );

static FF_Error_t FF_ExtendFile( FF_FILE * pxFile,
                                 uint32_t ulSize );

/*-----------------------------------------------------------*/

/**
 *	@brief	Converts STDIO mode strings into the equivalent FreeRTOS+FAT mode.
 *
 *	@param	pcMode	The mode string e.g. "rb" "rb+" "w" "a" "r" "w+" "a+" etc
 *
 *	@return	Returns the mode bits that should be passed to the FF_Open function.
 **/
uint8_t FF_GetModeBits( const char * pcMode )
{
    uint8_t ucModeBits = 0x00;

    while( *pcMode != '\0' )
    {
        switch( *pcMode )
        {
            case 'r': /* Allow Read. */
            case 'R':
                ucModeBits |= FF_MODE_READ;
                break;

            case 'w': /* Allow Write. */
            case 'W':
                ucModeBits |= FF_MODE_WRITE;
                ucModeBits |= FF_MODE_CREATE; /* Create if not exist. */
                ucModeBits |= FF_MODE_TRUNCATE;
                break;

            case 'a': /* Append new writes to the end of the file. */
            case 'A':
                ucModeBits |= FF_MODE_WRITE;
                ucModeBits |= FF_MODE_APPEND;
                ucModeBits |= FF_MODE_CREATE; /* Create if not exist. */
                break;

            case '+':                        /* Update the file, don't Append! */
                ucModeBits |= FF_MODE_READ;
                ucModeBits |= FF_MODE_WRITE; /* RW Mode. */
                break;

            case 'D':
                /* Internal use only! */
                ucModeBits |= FF_MODE_DIR;
                break;

            case 'b':
            case 'B':
                /* b|B flags not supported (Binary mode is native anyway). */
                break;

            default:
                break;
        }

        pcMode++;
    }

    return ucModeBits;
} /* FF_GetModeBits() */
/*-----------------------------------------------------------*/

static FF_FILE * prvAllocFileHandle( FF_IOManager_t * pxIOManager,
                                     FF_Error_t * pxError )
{
    FF_FILE * pxFile;

    pxFile = ffconfigMALLOC( sizeof( FF_FILE ) );

    if( pxFile == NULL )
    {
        *pxError = FF_createERR( FF_ERR_NOT_ENOUGH_MEMORY, FF_OPEN );
    }
    else
    {
        memset( pxFile, 0, sizeof( *pxFile ) );

        #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 )
        {
            pxFile->pucBuffer = ( uint8_t * ) ffconfigMALLOC( pxIOManager->usSectorSize );

            if( pxFile->pucBuffer != NULL )
            {
                memset( pxFile->pucBuffer, 0, pxIOManager->usSectorSize );
            }
            else
            {
                *pxError = FF_createERR( FF_ERR_NOT_ENOUGH_MEMORY, FF_OPEN );
                ffconfigFREE( pxFile );
                /* Make sure that NULL will be returned. */
                pxFile = NULL;
            }
        }
        #else /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */
        {
            /* Remove compiler warnings. */
            ( void ) pxIOManager;
        }
        #endif /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */
    }

    return pxFile;
} /* prvAllocFileHandle() */
/*-----------------------------------------------------------*/

/**
 * FF_Open() Mode Information
 * - FF_MODE_WRITE
 *   - Allows WRITE access to the file.
 *   .
 * - FF_MODE_READ
 *   - Allows READ access to the file.
 *   .
 * - FF_MODE_CREATE
 *   - Create file if it doesn't exist.
 *   .
 * - FF_MODE_TRUNCATE
 *   - Erase the file if it already exists and overwrite.
 *   *
 * - FF_MODE_APPEND
 *   - Causes all writes to occur at the end of the file. (Its impossible to overwrite other data in a file with this flag set).
 *   .
 * .
 *
 * Some sample modes:
 * - (FF_MODE_WRITE | FF_MODE_CREATE | FF_MODE_TRUNCATE)
 *   - Write access to the file. (Equivalent to "w").
 *   .
 * - (FF_MODE_WRITE | FF_MODE_READ)
 *   - Read and Write access to the file. (Equivalent to "rb+").
 *   .
 * - (FF_MODE_WRITE | FF_MODE_READ | FF_MODE_APPEND | FF_MODE_CREATE)
 *   - Read and Write append mode access to the file. (Equivalent to "a+").
 *   .
 * .
 * Be careful when choosing modes. For those using FF_Open() at the application layer
 * its best to use the provided FF_GetModeBits() function, as this complies to the same
 * behaviour as the stdio.h fopen() function.
 *
 **/

/**
 *	@brief	Opens a File for Access
 *
 *	@param	pxIOManager	FF_IOManager_t object that was created by FF_CreateIOManager().
 *	@param	pcPath		Path to the File or object.
 *	@param	ucMode		Access Mode required. Modes are a little complicated, the function FF_GetModeBits()
 *	@param	ucMode		will convert a stdio Mode string into the equivalent Mode bits for this parameter.
 *	@param	pxError		Pointer to a signed byte for error checking. Can be NULL if not required.
 *	@param	pxError		To be checked when a NULL pointer is returned.
 *
 *	@return	NULL pointer on error, in which case pxError should be checked for more information.
 **/
/* *INDENT-OFF* */
#if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
    FF_FILE * FF_Open( FF_IOManager_t * pxIOManager,
                       const FF_T_WCHAR * pcPath,
                       uint8_t ucMode,
                       FF_Error_t * pxError )
#else
    FF_FILE * FF_Open( FF_IOManager_t * pxIOManager,
                       const char * pcPath,
                       uint8_t ucMode,
                       FF_Error_t * pxError )
#endif
/* *INDENT-ON* */
{
    FF_FILE * pxFile = NULL;
    FF_FILE * pxFileChain;
    FF_DirEnt_t xDirEntry;
    uint32_t ulFileCluster;
    FF_Error_t xError;
    BaseType_t xIndex;
    FF_FindParams_t xFindParams;

    #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
        FF_T_WCHAR pcFileName[ ffconfigMAX_FILENAME ];
    #else
        char pcFileName[ ffconfigMAX_FILENAME ];
    #endif

    #if ( ffconfigPROTECT_FF_FOPEN_WITH_SEMAPHORE == 1 )
    {
        if( ( ucMode & FF_MODE_CREATE ) != 0U )
        {
            FF_PendSemaphore( pxIOManager->pvSemaphoreOpen );
        }
    }
    #endif

    memset( &xFindParams, '\0', sizeof( xFindParams ) );

    /* Inform the functions that the entry will be created if not found. */
    if( ( ucMode & FF_MODE_CREATE ) != 0 )
    {
        xFindParams.ulFlags |= FIND_FLAG_CREATE_FLAG;
    }

    if( pxIOManager == NULL )
    {
        /* Use the error function code 'FF_OPEN' as this static
         * function is only called from that function. */
        xError = FF_createERR( FF_ERR_NULL_POINTER, FF_OPEN );
    }

    #if ( ffconfigREMOVABLE_MEDIA != 0 )
        else if( ( pxIOManager->ucFlags & FF_IOMAN_DEVICE_IS_EXTRACTED ) != 0 )
        {
            xError = FF_createERR( FF_ERR_IOMAN_DRIVER_NOMEDIUM, FF_OPEN );
        }
    #endif /* ffconfigREMOVABLE_MEDIA */
    else
    {
        xError = FF_ERR_NONE;

        /* Let xIndex point to the last occurrence of '/' or '\',
         * to separate the path from the file name. */
        xIndex = ( BaseType_t ) STRLEN( pcPath );

        while( xIndex != 0 )
        {
            if( ( pcPath[ xIndex ] == '\\' ) || ( pcPath[ xIndex ] == '/' ) )
            {
                break;
            }

            xIndex--;
        }

        /* Copy the file name, i.e. the string that comes after the last separator. */
        STRNCPY( pcFileName, pcPath + xIndex + 1, ffconfigMAX_FILENAME - 1 );
        pcFileName[ ffconfigMAX_FILENAME - 1 ] = 0;

        if( xIndex == 0 )
        {
            /* Only for the root, the slash is part of the directory name.
             * 'xIndex' now equals to the length of the path name. */
            xIndex = 1;
        }

        /* FF_CreateShortName() might set flags FIND_FLAG_FITS_SHORT and FIND_FLAG_SIZE_OK. */
        FF_CreateShortName( &xFindParams, pcFileName );

        /* Lookup the path and find the cluster pointing to the directory: */
        xFindParams.ulDirCluster = FF_FindDir( pxIOManager, pcPath, ( uint16_t ) xIndex, &xError );

        if( xFindParams.ulDirCluster == 0ul )
        {
            if( ( ucMode & FF_MODE_WRITE ) != 0 )
            {
                FF_PRINTF( "FF_Open[%s]: Path not found\n", pcPath );
            }

            /* The user tries to open a file but the path leading to the file does not exist. */
        }
        else if( FF_isERR( xError ) == pdFALSE )
        {
            /* Allocate an empty file handle and buffer space for 'unaligned access'. */
            pxFile = prvAllocFileHandle( pxIOManager, &xError );
        }
    }

    if( FF_isERR( xError ) == pdFALSE )
    {
        /* Copy the Mode Bits. */
        pxFile->ucMode = ucMode;

        /* See if the file does exist within the given directory. */
        ulFileCluster = FF_FindEntryInDir( pxIOManager, &xFindParams, pcFileName, 0x00, &xDirEntry, &xError );

        if( ulFileCluster == 0ul )
        {
            /* If cluster 0 was returned, it might be because the file has no allocated cluster,
             * i.e. only a directory entry and no stored data. */
            if( STRLEN( pcFileName ) == STRLEN( xDirEntry.pcFileName ) )
            {
                if( ( xDirEntry.ulFileSize == 0 ) && ( FF_strmatch( pcFileName, xDirEntry.pcFileName, ( BaseType_t ) STRLEN( pcFileName ) ) == pdTRUE ) )
                {
                    /* It is the file, give it a pseudo cluster number '1'. */
                    ulFileCluster = 1;
                    /* And reset any error. */
                    xError = FF_ERR_NONE;
                }
            }
        }

        /* Test 'ulFileCluster' again, it might have been changed. */
        if( ulFileCluster == 0ul )
        {
            /* The path is found, but it does not contain the file name yet.
             * Maybe the user wants to create it? */
            if( ( ucMode & FF_MODE_CREATE ) == 0 )
            {
                xError = FF_createERR( FF_ERR_FILE_NOT_FOUND, FF_OPEN );
            }
            else
            {
                ulFileCluster = FF_CreateFile( pxIOManager, &xFindParams, pcFileName, &xDirEntry, &xError );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xDirEntry.usCurrentItem += 1;
                }
            }
        }
    }

    if( FF_isERR( xError ) == pdFALSE )
    {
        /* Now the file exists, or it has been created.
         * Check if the Mode flags are allowed: */
        if( ( xDirEntry.ucAttrib == FF_FAT_ATTR_DIR ) && ( ( ucMode & FF_MODE_DIR ) == 0 ) )
        {
            /* Not the object, File Not Found! */
            xError = FF_createERR( FF_ERR_FILE_OBJECT_IS_A_DIR, FF_OPEN );
        }
        /*---------- Ensure Read-Only files don't get opened for Writing. */
        else if( ( ( ucMode & ( FF_MODE_WRITE | FF_MODE_APPEND ) ) != 0 ) && ( ( xDirEntry.ucAttrib & FF_FAT_ATTR_READONLY ) != 0 ) )
        {
            xError = FF_createERR( FF_ERR_FILE_IS_READ_ONLY, FF_OPEN );
        }
    }

    if( FF_isERR( xError ) == pdFALSE )
    {
        pxFile->pxIOManager = pxIOManager;
        pxFile->ulFilePointer = 0;

        /* Despite the warning output by MSVC - it is not possible to get here
         * if xDirEntry has not been initialised. */
        pxFile->ulObjectCluster = xDirEntry.ulObjectCluster;
        pxFile->ulFileSize = xDirEntry.ulFileSize;
        pxFile->ulCurrentCluster = 0;
        pxFile->ulAddrCurrentCluster = pxFile->ulObjectCluster;

        pxFile->pxNext = NULL;
        pxFile->ulDirCluster = xFindParams.ulDirCluster;
        pxFile->usDirEntry = xDirEntry.usCurrentItem - 1;
        pxFile->ulChainLength = 0;
        pxFile->ulEndOfChain = 0;
        pxFile->ulValidFlags &= ~( FF_VALID_FLAG_DELETED );

        /* Add pxFile onto the end of our linked list of FF_FILE objects.
         * But first make sure that there are not 2 handles with write access
         * to the same object. */
        FF_PendSemaphore( pxIOManager->pvSemaphore );
        {
            pxFileChain = ( FF_FILE * ) pxIOManager->FirstFile;

            if( pxFileChain == NULL )
            {
                pxIOManager->FirstFile = pxFile;
            }
            else
            {
                for( ; ; )
                {
                    /* See if two file handles point to the same object. */
                    if( ( pxFileChain->ulObjectCluster == pxFile->ulObjectCluster ) &&
                        ( pxFileChain->ulDirCluster == pxFile->ulDirCluster ) &&
                        ( pxFileChain->usDirEntry == pxFile->usDirEntry ) )
                    {
                        /* Fail if any of the two has write access to the object. */
                        if( ( ( pxFileChain->ucMode | pxFile->ucMode ) & ( FF_MODE_WRITE | FF_MODE_APPEND ) ) != 0 )
                        {
                            /* File is already open! DON'T ALLOW IT! */
                            xError = FF_createERR( FF_ERR_FILE_ALREADY_OPEN, FF_OPEN );
                            break;
                        }
                    }

                    if( pxFileChain->pxNext == NULL )
                    {
                        pxFileChain->pxNext = pxFile;
                        break;
                    }

                    pxFileChain = ( FF_FILE * ) pxFileChain->pxNext;
                }
            }
        }

        FF_ReleaseSemaphore( pxIOManager->pvSemaphore );
    }

    if( FF_isERR( xError ) == pdFALSE )
    {
        /* If the file is opened with the truncate flag, truncate its contents. */
        if( ( ucMode & FF_MODE_TRUNCATE ) != 0 )
        {
            /* Set the current size and position to zero. */
            pxFile->ulFileSize = 0;
            pxFile->ulFilePointer = 0;
        }
    }

    if( FF_isERR( xError ) != pdFALSE )
    {
        if( pxFile != NULL )
        {
            #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 )
            {
                ffconfigFREE( pxFile->pucBuffer );
            }
            #endif
            ffconfigFREE( pxFile );
        }

        pxFile = NULL;
    }

    #if ( ffconfigPROTECT_FF_FOPEN_WITH_SEMAPHORE == 1 )
    {
        if( ( ucMode & FF_MODE_CREATE ) != 0U )
        {
            FF_ReleaseSemaphore( pxIOManager->pvSemaphoreOpen );
        }
    }
    #endif

    if( pxError != NULL )
    {
        *pxError = xError;
    }

    return pxFile;
} /* FF_Open() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Tests if a Directory contains any other files or folders.
 *
 *	@param	pxIOManager	FF_IOManager_t object returned from the FF_CreateIOManager() function.
 *
 **/
/* *INDENT-OFF* */
#if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
    BaseType_t FF_isDirEmpty( FF_IOManager_t * pxIOManager,
                              const FF_T_WCHAR * pcPath )
#else
    BaseType_t FF_isDirEmpty( FF_IOManager_t * pxIOManager,
                              const char * pcPath )
#endif
/* *INDENT-ON* */
{
    FF_DirEnt_t xDirEntry;
    FF_Error_t xError = FF_ERR_NONE;
    BaseType_t xReturn;

    if( pxIOManager == NULL )
    {
        xReturn = pdFALSE;
    }
    else
    {
        xError = FF_FindFirst( pxIOManager, &xDirEntry, pcPath );

        /* Assume the directory is empty until a file is
         * encountered with a name other than ".." or "." */
        xReturn = pdTRUE;

        while( xError == 0 )
        {
            /* As we can't be sure the first 2 entries contain
             * "." and "..", check it, not just count them
             */
            #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
                if( ( wcscmp( xDirEntry.pcFileName, L".." ) != 0 ) && ( wcscmp( xDirEntry.pcFileName, L"." ) != 0 ) )
            #else
                if( ( strcmp( xDirEntry.pcFileName, ".." ) != 0 ) && ( strcmp( xDirEntry.pcFileName, "." ) != 0 ) )
            #endif
            {
                xReturn = pdFALSE;
                break;
            }

            xError = FF_FindNext( pxIOManager, &xDirEntry );
        }
    }

    return xReturn;
} /* FF_isDirEmpty() */
/*-----------------------------------------------------------*/

#if ( ffconfigPATH_CACHE != 0 )
    /* _HT_ After a directory has been renamed, the path cache becomes out-of-date */
    #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
        static void FF_RmPathCache( FF_IOManager_t * pxIOManager,
                                    const FF_T_WCHAR * pcPath )
    #else
        static void FF_RmPathCache( FF_IOManager_t * pxIOManager,
                                    const char * pcPath )
    #endif
    {
        /*
         * The directory 'path' will be removed or renamed
         * now clear all entries starting with 'path' in the path cache
         */
        UBaseType_t pathLen = STRLEN( pcPath );

        FF_PendSemaphore( pxIOManager->pvSemaphore );
        {
            for( UBaseType_t xIndex = 0; xIndex < ffconfigPATH_CACHE_DEPTH; xIndex++ )
            {
                UBaseType_t len2 = STRLEN( pxIOManager->xPartition.pxPathCache[ xIndex ].pcPath );

                if( ( len2 >= pathLen ) && FF_strmatch( pxIOManager->xPartition.pxPathCache[ xIndex ].pcPath, pcPath, ( BaseType_t ) pathLen ) )
                {
                    pxIOManager->xPartition.pxPathCache[ xIndex ].pcPath[ 0 ] = '\0';
                    pxIOManager->xPartition.pxPathCache[ xIndex ].ulDirCluster = 0;
                }
            }
        }

        FF_ReleaseSemaphore( pxIOManager->pvSemaphore );
    }
#endif /* ffconfigPATH_CACHE */
/*-----------------------------------------------------------*/

/* *INDENT-OFF* */
#if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
    FF_Error_t FF_RmDir( FF_IOManager_t * pxIOManager,
                         const FF_T_WCHAR * pcPath )
#else
    FF_Error_t FF_RmDir( FF_IOManager_t * pxIOManager,
                         const char * pcPath )
#endif
/* *INDENT-ON* */
{
    FF_FILE * pxFile;
    uint8_t ucEntryBuffer[ 32 ];
    FF_FetchContext_t xFetchContext;
    FF_Error_t xError = FF_ERR_NONE;

    if( pxIOManager == NULL )
    {
        xError = FF_createERR( FF_ERR_NULL_POINTER, FF_RMDIR );
    }

    #if ( ffconfigREMOVABLE_MEDIA != 0 )
        else if( ( pxIOManager->ucFlags & FF_IOMAN_DEVICE_IS_EXTRACTED ) != 0 )
        {
            xError = FF_createERR( FF_ERR_IOMAN_DRIVER_NOMEDIUM, FF_RMDIR );
        }
    #endif /* ffconfigREMOVABLE_MEDIA */
    else
    {
        pxFile = FF_Open( pxIOManager, pcPath, FF_MODE_DIR, &xError );

        if( pxFile != NULL )
        {
            pxFile->ulValidFlags |= FF_VALID_FLAG_DELETED;

            /* Clear the struct to allow a call to FF_CleanupEntryFetch() in any
             * state. */
            memset( &xFetchContext, '\0', sizeof( xFetchContext ) );

            /* This task will get the unique right to change directories. */
            FF_LockDirectory( pxIOManager );

            do /* while( pdFALSE ) */
            {
                /* This while loop is only introduced to be able to use break
                 * statements. */
                if( FF_isDirEmpty( pxIOManager, pcPath ) == pdFALSE )
                {
                    xError = FF_createERR( FF_ERR_DIR_NOT_EMPTY, FF_RMDIR );
                    break;
                }

                FF_LockFAT( pxIOManager );
                #if ( ffconfigHASH_CACHE != 0 )
                {
                    /* A directory is removed so invalidate any hash table
                     * referring to this directory. */
                    FF_UnHashDir( pxIOManager, pxFile->ulObjectCluster );
                }
                #endif /* ffconfigHASH_CACHE */
                {
                    /* Add parameter 0 to delete the entire chain!
                     * The actual directory entries on disk will be freed. */
                    xError = FF_UnlinkClusterChain( pxIOManager, pxFile->ulObjectCluster, 0 );
                }
                FF_UnlockFAT( pxIOManager );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                /* Now remove this directory from its parent directory.
                 * Initialise the dirent Fetch Context object for faster removal of
                 * dirents. */
                xError = FF_InitEntryFetch( pxIOManager, pxFile->ulDirCluster, &xFetchContext );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                #if ( ffconfigHASH_CACHE != 0 )
                {
                    /* Invalidate any hash table of the parent directory
                     * as well. */
                    FF_UnHashDir( pxIOManager, pxFile->ulDirCluster );
                }
                #endif /* ffconfigHASH_CACHE */

                /* Edit the Directory Entry, so it will show as deleted.
                 * First remove the LFN entries: */
                xError = FF_RmLFNs( pxIOManager, pxFile->usDirEntry, &xFetchContext );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                /* And remove the Short file name entry: */
                xError = FF_FetchEntryWithContext( pxIOManager, pxFile->usDirEntry, &xFetchContext, ucEntryBuffer );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    ucEntryBuffer[ 0 ] = FF_FAT_DELETED;
                    FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_HIGH, ( uint32_t ) 0ul );
                    FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_LOW, ( uint32_t ) 0ul );

                    xError = FF_PushEntryWithContext( pxIOManager, pxFile->usDirEntry, &xFetchContext, ucEntryBuffer );
                }

                if( FF_isERR( xError ) )
                {
                    break;
                }

                #if ( ffconfigPATH_CACHE != 0 )
                {
                    /* We're removing a directory which might contain
                     * subdirectories.  Instead of iterating through all
                     * subdirectories, just clear the path cache. */
                    FF_RmPathCache( pxIOManager, pcPath );
                }
                #endif
            } while( pdFALSE );

            {
                FF_Error_t xTempError;
                xTempError = FF_CleanupEntryFetch( pxIOManager, &xFetchContext );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xError = xTempError;
                }

                FF_UnlockDirectory( pxIOManager );

                /* Free the file pointer resources. */
                xTempError = FF_Close( pxFile );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xError = xTempError;
                }

                xTempError = FF_FlushCache( pxIOManager );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xError = xTempError;
                }
            }
        } /* if( pxFile != NULL ) */
    }     /* else if( pxIOManager != NULL ) */

    return xError;
} /* FF_RmDir() */
/*-----------------------------------------------------------*/

/* *INDENT-OFF* */
#if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
    FF_Error_t FF_RmFile( FF_IOManager_t * pxIOManager,
                          const FF_T_WCHAR * pcPath )
#else
    FF_Error_t FF_RmFile( FF_IOManager_t * pxIOManager,
                          const char * pcPath )
#endif
/* *INDENT-ON* */
{
    FF_FILE * pxFile;
    FF_Error_t xError = FF_ERR_NONE;
    uint8_t ucEntryBuffer[ 32 ];
    FF_FetchContext_t xFetchContext;

    /* Opening the file-to-be-deleted in WR mode has two advantages:
     * 1. The file handle gives all necessary information to delete it such
     * as the data clusters and directory entries.
     * 2. The file is now locked, it can not be opened by another task. */
    pxFile = FF_Open( pxIOManager, pcPath, FF_MODE_WRITE, &xError );

    if( pxFile != NULL )
    {
        /* FF_Close() will see this flag and won't do any disc access. */
        pxFile->ulValidFlags |= FF_VALID_FLAG_DELETED;

        /* Ensure there is actually a cluster chain to delete! */
        if( pxFile->ulObjectCluster != 0 )
        {
            /* Lock the FAT so its thread-safe. */
            FF_LockFAT( pxIOManager );
            {
                /* 0 to delete the entire chain! */
                xError = FF_UnlinkClusterChain( pxIOManager, pxFile->ulObjectCluster, 0 );
            }
            FF_UnlockFAT( pxIOManager );
        }

        if( FF_isERR( xError ) == pdFALSE )
        {
            /* Clear the struct to allow a call to FF_CleanupEntryFetch() in any
             * state. */
            memset( &xFetchContext, '\0', sizeof( xFetchContext ) );

            /* Get sole access to "directory changes" */
            FF_LockDirectory( pxIOManager );

            /* Edit the Directory Entry! (So it appears as deleted); */
            do
            {
                xError = FF_InitEntryFetch( pxIOManager, pxFile->ulDirCluster, &xFetchContext );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                #if ( ffconfigHASH_CACHE != 0 )
                {
                    FF_UnHashDir( pxIOManager, pxFile->ulDirCluster );
                }
                #endif /* ffconfigHASH_CACHE */
                /* Remove LFN entries, if any. */
                xError = FF_RmLFNs( pxIOManager, ( uint16_t ) pxFile->usDirEntry, &xFetchContext );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                /* Remove the Short file name entry. */
                xError = FF_FetchEntryWithContext( pxIOManager, pxFile->usDirEntry, &xFetchContext, ucEntryBuffer );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    ucEntryBuffer[ 0 ] = FF_FAT_DELETED;
                    FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_HIGH, ( uint32_t ) 0ul );
                    FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_LOW, ( uint32_t ) 0ul );

                    xError = FF_PushEntryWithContext( pxIOManager, pxFile->usDirEntry, &xFetchContext, ucEntryBuffer );
                }
            } while( pdFALSE );

            {
                FF_Error_t xTempError;
                xTempError = FF_CleanupEntryFetch( pxIOManager, &xFetchContext );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xError = xTempError;
                }

                FF_UnlockDirectory( pxIOManager );

                /* Free the file pointer resources. */
                xTempError = FF_Close( pxFile );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xError = xTempError;
                }

                xTempError = FF_FlushCache( pxIOManager );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xError = xTempError;
                }
            }
        }
    } /* if( pxFile != NULL ) */

    return xError;
} /* FF_RmFile() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Moves a file or directory from source to destination.
 *
 *	@param	pxIOManager				The FF_IOManager_t object pointer.
 *	@param	szSourceFile		String of the source file to be moved or renamed.
 *	@param	szDestinationFile	String of the destination file to where the source should be moved or renamed.
 *
 *	@retval	FF_ERR_NONE on success.
 *	@retval FF_ERR_FILE_DESTINATION_EXISTS if the destination file exists.
 *	@retval FF_ERR_FILE_COULD_NOT_CREATE_DIRENT if dirent creation failed (fatal error!).
 *	@retval FF_ERR_FILE_DIR_NOT_FOUND if destination directory was not found.
 *	@retval FF_ERR_FILE_SOURCE_NOT_FOUND if the source file was not found.
 *
 **/

/* *INDENT-OFF* */
#if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
    FF_Error_t FF_Move( FF_IOManager_t * pxIOManager,
                        const FF_T_WCHAR * szSourceFile,
                        const FF_T_WCHAR * szDestinationFile,
                        BaseType_t xDeleteIfExists )
#else
    FF_Error_t FF_Move( FF_IOManager_t * pxIOManager,
                        const char * szSourceFile,
                        const char * szDestinationFile,
                        BaseType_t xDeleteIfExists )
#endif
/* *INDENT-ON* */
{
    FF_Error_t xError;
    FF_FILE * pSrcFile, * pxDestFile;
    FF_DirEnt_t xMyFile;
    uint8_t ucEntryBuffer[ 32 ];
    UBaseType_t xIndex;
    uint32_t ulDirCluster = 0ul;
    FF_FetchContext_t xFetchContext;

    #if ( ffconfigPATH_CACHE != 0 )
        BaseType_t xIsDirectory = pdFALSE;
    #endif

    memset( &xFetchContext, '\0', sizeof( xFetchContext ) );

    if( pxIOManager == NULL )
    {
        xError = FF_createERR( FF_ERR_NULL_POINTER, FF_MOVE );
    }

    #if ( ffconfigREMOVABLE_MEDIA != 0 )
        else if( ( pxIOManager->ucFlags & FF_IOMAN_DEVICE_IS_EXTRACTED ) != 0 )
        {
            xError = FF_createERR( FF_ERR_IOMAN_DRIVER_NOMEDIUM, FF_MOVE );
        }
    #endif /* ffconfigREMOVABLE_MEDIA */
    else
    {
        /* Check destination file doesn't exist! */
        pxDestFile = FF_Open( pxIOManager, szDestinationFile, FF_MODE_READ, &xError );

        if( ( pxDestFile != NULL ) || ( FF_GETERROR( xError ) == FF_ERR_FILE_OBJECT_IS_A_DIR ) )
        {
            xError = FF_createERR( FF_ERR_FILE_DESTINATION_EXISTS, FF_MOVE );

            if( pxDestFile != NULL )
            {
                FF_Close( pxDestFile );

                if( xDeleteIfExists != pdFALSE )
                {
                    xError = FF_RmFile( pxIOManager, szDestinationFile );
                }
            }
        }
        else
        {
            /* Discard the error set by FF_Open().
             * The target file (or directory) is not found: continue renaming. */
            xError = FF_ERR_NONE;
        }
    }

    if( FF_isERR( xError ) == pdFALSE )
    {
        /* About to move/rename 'szSourceFile'.  When opening it with 'FF_MODE_WRITE'
         * only succeeds if it has no other open handle to it. */
        pSrcFile = FF_Open( pxIOManager, szSourceFile, FF_MODE_WRITE, &xError );

        if( FF_GETERROR( xError ) == FF_ERR_FILE_OBJECT_IS_A_DIR )
        {
            /* Open a directory for moving! */
            pSrcFile = FF_Open( pxIOManager, szSourceFile, FF_MODE_DIR, &xError );
            #if ( ffconfigPATH_CACHE != 0 )
                xIsDirectory = pdTRUE;
            #endif
        }

        if( pSrcFile != NULL )
        {
            /* Collect information about the current directory entry. */
            xError = FF_InitEntryFetch( pxIOManager, pSrcFile->ulDirCluster, &xFetchContext );

            if( FF_isERR( xError ) == pdFALSE )
            {
                xError = FF_FetchEntryWithContext( pxIOManager, pSrcFile->usDirEntry, &xFetchContext, ucEntryBuffer );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xMyFile.ucAttrib = FF_getChar( ucEntryBuffer, ( uint16_t ) ( FF_FAT_DIRENT_ATTRIB ) );
                    xMyFile.ulFileSize = pSrcFile->ulFileSize;
                    xMyFile.ulObjectCluster = pSrcFile->ulObjectCluster;
                    xMyFile.usCurrentItem = 0;

                    xIndex = ( UBaseType_t ) STRLEN( szDestinationFile );

                    while( xIndex != 0 )
                    {
                        if( ( szDestinationFile[ xIndex ] == '\\' ) || ( szDestinationFile[ xIndex ] == '/' ) )
                        {
                            break;
                        }

                        xIndex--;
                    }

                    /* Copy the base name of the destination file. */
                    STRNCPY( xMyFile.pcFileName, ( szDestinationFile + xIndex + 1 ), ffconfigMAX_FILENAME - 1 );
                    xMyFile.pcFileName[ ffconfigMAX_FILENAME - 1 ] = 0;

                    if( xIndex == 0 )
                    {
                        xIndex = 1;
                    }

                    /* Find the (cluster of the) directory in which the target file will be located.
                     * It must exist before calling FF_Move(). */
                    ulDirCluster = FF_FindDir( pxIOManager, szDestinationFile, ( uint16_t ) xIndex, &xError );
                }
            }
        }

        if( FF_isERR( xError ) == pdFALSE )
        {
            if( ulDirCluster != 0ul )
            {
                FF_FindParams_t xFindParams;
                memset( &xFindParams, '\0', sizeof( xFindParams ) );

                /* Clean up because FF_CreateDirent might want to write to the same sector. */
                xError = FF_CleanupEntryFetch( pxIOManager, &xFetchContext );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    /* Destination directory was found, we can now create the new entry. */
                    xFindParams.ulDirCluster = ulDirCluster;
                    xError = FF_CreateDirent( pxIOManager, &xFindParams, &xMyFile );
                }

                if( FF_isERR( xError ) == pdFALSE )
                {
                    /* Edit the Directory Entry! (So it appears as deleted); */
                    FF_LockDirectory( pxIOManager );
                    {
                        xError = FF_RmLFNs( pxIOManager, pSrcFile->usDirEntry, &xFetchContext );

                        if( FF_isERR( xError ) == pdFALSE )
                        {
                            xError = FF_FetchEntryWithContext( pxIOManager, pSrcFile->usDirEntry, &xFetchContext, ucEntryBuffer );

                            if( FF_isERR( xError ) == pdFALSE )
                            {
                                FF_Error_t xTempError;
                                ucEntryBuffer[ 0 ] = FF_FAT_DELETED;
                                FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_HIGH, ( uint32_t ) 0ul );
                                FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_LOW, ( uint32_t ) 0ul );

                                xError = FF_PushEntryWithContext( pxIOManager, pSrcFile->usDirEntry, &xFetchContext, ucEntryBuffer );
                                /* The contents of 'xFetchContext' has changed, flush it to disk now. */
                                xTempError = FF_CleanupEntryFetch( pxIOManager, &xFetchContext );

                                if( FF_isERR( xError ) == pdFALSE )
                                {
                                    xError = xTempError;
                                }
                            }
                        }
                    }
                    FF_UnlockDirectory( pxIOManager );
                }

                #if ( ffconfigPATH_CACHE != 0 )
                {
                    if( xIsDirectory != 0 )
                    {
                        /* We've renamed a directory which might contain
                         * subdirectories.  To avoid having false entries, clear
                         * the path cache. */
                        FF_RmPathCache( pxIOManager, szSourceFile );
                    }
                }
                #endif
            }
            else /* ulDirCluster == 0ul */
            {
                xError = FF_createERR( FF_ERR_FILE_DIR_NOT_FOUND, FF_MOVE );
            }
        }

        if( pSrcFile != NULL )
        {
            /* The source file was opened in WRITE mode just to lock it.
             * Now clear the write flags to avoid writing back any changes. */
            pSrcFile->ucMode &= ~( FF_MODE_WRITE | FF_MODE_APPEND | FF_MODE_CREATE );
            FF_Close( pSrcFile );
        }
    }

    {
        FF_Error_t xTempError;

        xTempError = FF_FlushCache( pxIOManager );

        if( FF_isERR( xError ) == pdFALSE )
        {
            xError = xTempError;
        }

        xTempError = FF_CleanupEntryFetch( pxIOManager, &xFetchContext );

        if( FF_isERR( xError ) == pdFALSE )
        {
            xError = xTempError;
        }
    }

    return xError;
} /* FF_Move() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Get's the next Entry based on the data recorded in the FF_DirEnt_t object.
 *
 *	@param	pxFile	FF_FILE object that was created by FF_Open().
 *
 *	@retval pdTRUE if End of File was reached. pdFALSE if not.
 *	@retval pdFALSE if a null pointer was provided.
 *
 **/
BaseType_t FF_isEOF( FF_FILE * pxFile )
{
    BaseType_t xReturn;

    if( ( pxFile != NULL ) && ( pxFile->ulFilePointer >= pxFile->ulFileSize ) )
    {
        xReturn = pdTRUE;
    }
    else
    {
        xReturn = pdFALSE;
    }

    return xReturn;
} /* FF_isEOF() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Checks the number of bytes left on a read handle
 *
 *	@param	pxFile		An open file handle
 *
 *	@return	Less than zero: an error code, or Number of bytes left to read from handle
 **/
int32_t FF_BytesLeft( FF_FILE * pxFile )
{
    BaseType_t xReturn;

    if( pxFile == NULL )
    {
        xReturn = FF_createERR( FF_ERR_NULL_POINTER, FF_BYTESLEFT );
    }
    else if( ( pxFile->ucMode & FF_MODE_READ ) == 0 )
    {
        xReturn = FF_createERR( FF_ERR_FILE_NOT_OPENED_IN_READ_MODE, FF_BYTESLEFT );
    }
    else if( pxFile->ulFilePointer >= pxFile->ulFileSize )
    {
        xReturn = 0;
    }
    else
    {
        xReturn = pxFile->ulFileSize - pxFile->ulFilePointer;
    }

    return ( int32_t ) xReturn;
} /* FF_BytesLeft() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Returns the file size of a read handle
 *
 *	@param	    pxFile		An open file handle
 *  @param[out] pulSize     Writes # of bytes in a file to the parameter.
 *
 *	@return	Less than zero: an error code, or Number of bytes left to read from handle
 **/
FF_Error_t FF_GetFileSize( FF_FILE * pxFile,
                           uint32_t * pulSize ) /* Writes # of bytes in a file to the parameter. */
{
    FF_Error_t xReturn;

    if( pxFile == NULL )
    {
        xReturn = FF_createERR( FF_ERR_NULL_POINTER, FF_BYTESLEFT );
        *( pulSize ) = ( uint32_t ) 0u;
    }
    else if( FF_isERR( FF_CheckValid( pxFile ) ) )
    {
        xReturn = FF_createERR( FF_ERR_FILE_BAD_HANDLE, FF_BYTESLEFT );
        *( pulSize ) = ( uint32_t ) 0u;
    }
    else
    {
        xReturn = FF_ERR_NONE;
        *( pulSize ) = pxFile->ulFileSize;
    }

    return xReturn;
} /* FF_GetFileSize */

int32_t FF_FileSize( FF_FILE * pxFile )
{
    uint32_t ulLength;
    FF_Error_t xResult;

    /* Function is deprecated. Please use FF_GetFileSize(). */
    xResult = FF_GetFileSize( pxFile, &( ulLength ) );

    if( FF_isERR( xResult ) == 0 )
    {
        xResult = ( int32_t ) ulLength;
    }

    return ( int32_t ) xResult;
} /* FF_FileSize() */
/*-----------------------------------------------------------*/

static uint32_t FF_GetSequentialClusters( FF_IOManager_t * pxIOManager,
                                          uint32_t ulStartCluster,
                                          uint32_t ulLimit,
                                          FF_Error_t * pxError )
{
    uint32_t ulCurrentCluster;
    uint32_t ulNextCluster = ulStartCluster;
    uint32_t ulIndex = 0;

    FF_FATBuffers_t xFATBuffers;

    FF_InitFATBuffers( &xFATBuffers, FF_MODE_READ );

    *pxError = FF_ERR_NONE;

    FF_LockFAT( pxIOManager );

    do
    {
        ulCurrentCluster = ulNextCluster;
        ulNextCluster = FF_getFATEntry( pxIOManager, ulCurrentCluster, pxError, &xFATBuffers );

        if( FF_isERR( *pxError ) )
        {
            ulIndex = 0;
            break;
        }

        if( ulNextCluster == ( ulCurrentCluster + 1 ) )
        {
            ulIndex++;
        }
        else
        {
            break;
        }

        if( ( ulLimit != 0 ) && ( ulIndex == ulLimit ) )
        {
            break;
        }
    }
    while( ulNextCluster == ( ulCurrentCluster + 1 ) );

    FF_UnlockFAT( pxIOManager );

    *pxError = FF_ReleaseFATBuffers( pxIOManager, &xFATBuffers );

    return ulIndex;
} /* FF_GetSequentialClusters() */
/*-----------------------------------------------------------*/

static FF_Error_t FF_ReadClusters( FF_FILE * pxFile,
                                   uint32_t ulCount,
                                   uint8_t * buffer )
{
    uint32_t ulSectors;
    uint32_t ulSequentialClusters = 0;
    uint32_t ulItemLBA;
    FF_Error_t xError = FF_ERR_NONE;

    while( ulCount != 0 )
    {
        if( ( ulCount - 1 ) > 0 )
        {
            ulSequentialClusters =
                FF_GetSequentialClusters( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster, ulCount - 1, &xError );

            if( FF_isERR( xError ) )
            {
                break;
            }
        }

        ulSectors = ( ulSequentialClusters + 1 ) * pxFile->pxIOManager->xPartition.ulSectorsPerCluster;
        ulItemLBA = FF_Cluster2LBA( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster );
        ulItemLBA = FF_getRealLBA( pxFile->pxIOManager, ulItemLBA );

        xError = FF_BlockRead( pxFile->pxIOManager, ulItemLBA, ulSectors, buffer, pdFALSE );

        if( FF_isERR( xError ) )
        {
            break;
        }

        ulCount -= ( ulSequentialClusters + 1 );

        FF_LockFAT( pxFile->pxIOManager );
        {
            pxFile->ulAddrCurrentCluster =
                FF_TraverseFAT( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster, ulSequentialClusters + 1, &xError );
        }
        FF_UnlockFAT( pxFile->pxIOManager );

        if( FF_isERR( xError ) )
        {
            break;
        }

        pxFile->ulCurrentCluster += ( ulSequentialClusters + 1 );
        buffer += ulSectors * pxFile->pxIOManager->usSectorSize;
        ulSequentialClusters = 0;
    }

    return xError;
} /* FF_ReadClusters ()*/
/*-----------------------------------------------------------*/

static FF_Error_t FF_ExtendFile( FF_FILE * pxFile,
                                 uint32_t ulSize )
{
    FF_IOManager_t * pxIOManager = pxFile->pxIOManager;
    uint32_t ulBytesPerCluster = pxIOManager->xPartition.usBlkSize * pxIOManager->xPartition.ulSectorsPerCluster;
    uint32_t ulTotalClustersNeeded = ( ulSize + ulBytesPerCluster - 1 ) / ulBytesPerCluster;
    uint32_t ulClusterToExtend;
/* Initialise xIndex just for the compiler. */
    BaseType_t xIndex = 0;
    FF_DirEnt_t xOriginalEntry;
    FF_Error_t xError = FF_ERR_NONE;
    FF_FATBuffers_t xFATBuffers;

    if( ( pxFile->ucMode & FF_MODE_WRITE ) != FF_MODE_WRITE )
    {
        xError = FF_createERR( FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE, FF_EXTENDFILE );
    }
    else
    {
        if( ( pxFile->ulFileSize == 0 ) && ( pxFile->ulObjectCluster == 0 ) )
        {
            /* If there is no object cluster yet, create it.*/
            pxFile->ulAddrCurrentCluster = FF_CreateClusterChain( pxFile->pxIOManager, &xError );

            if( FF_isERR( xError ) == pdFALSE )
            {
                /* The directory denotes the address of the first data cluster of every file.
                 * Now change it to 'ulAddrCurrentCluster': */
                xError = FF_GetEntry( pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xOriginalEntry.ulObjectCluster = pxFile->ulAddrCurrentCluster;
                    xError = FF_PutEntry( pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry, NULL );

                    if( FF_isERR( xError ) == pdFALSE )
                    {
                        pxFile->ulObjectCluster = pxFile->ulAddrCurrentCluster;
                        pxFile->ulChainLength = 1;
                        pxFile->ulCurrentCluster = 0;
                        pxFile->ulEndOfChain = pxFile->ulAddrCurrentCluster;
                    }
                }
            }
        }
        else
        {
            /* This file already has at least one cluster. */
        }
    }

    if( FF_isERR( xError ) == pdFALSE )
    {
        if( pxFile->ulChainLength == 0 )
        {
            /* This is the first extension requiring the chain length.
             * Calculate it now: */
            pxFile->ulChainLength = FF_GetChainLength( pxIOManager, pxFile->ulObjectCluster, &pxFile->ulEndOfChain, &xError );
        }
    }

    if( ( FF_isERR( xError ) == pdFALSE ) && ( ulTotalClustersNeeded > pxFile->ulChainLength ) )
    {
        uint32_t ulCurrentCluster, ulNextCluster;

        ulClusterToExtend = ( ulTotalClustersNeeded - pxFile->ulChainLength );
        /* Now the file has at least 1 cluster, but it needs more clusters. */
        ulNextCluster = pxFile->ulAddrCurrentCluster;
        FF_LockFAT( pxIOManager );

        ulCurrentCluster = FF_FindEndOfChain( pxIOManager, ulNextCluster, &xError );

        if( FF_isERR( xError ) == pdFALSE )
        {
            for( xIndex = 0; xIndex < ( BaseType_t ) ulClusterToExtend; xIndex++ )
            {
                /* In FF_ExtendFile() */
                ulNextCluster = FF_FindFreeCluster( pxIOManager, &xError, pdTRUE );

                if( ( FF_isERR( xError ) == pdFALSE ) && ( ulNextCluster == 0UL ) )
                {
                    xError = FF_createERR( FF_ERR_FAT_NO_FREE_CLUSTERS, FF_EXTENDFILE );
                }

                if( FF_isERR( xError ) )
                {
                    break;
                }

                /* Can not use this buffer earlier because of FF_FindEndOfChain/FF_FindFreeCluster */
                FF_InitFATBuffers( &xFATBuffers, FF_MODE_WRITE );
                xError = FF_putFATEntry( pxIOManager, ulCurrentCluster, ulNextCluster, &xFATBuffers );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                xError = FF_ReleaseFATBuffers( pxIOManager, &xFATBuffers );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                ulCurrentCluster = ulNextCluster;
            }

            if( FF_isERR( xError ) == pdFALSE )
            {
                pxFile->ulEndOfChain = ulCurrentCluster;
            }

            pxFile->ulChainLength += xIndex;
        }

        FF_UnlockFAT( pxIOManager );

        {
            FF_Error_t xTempError;
            xTempError = FF_DecreaseFreeClusters( pxIOManager, ( uint32_t ) xIndex ); /* Keep Tab of Numbers for fast FreeSize() */

            if( FF_isERR( xError ) == pdFALSE )
            {
                xError = xTempError;
            }
        }

        /* We must ensure that the ulAddrCurrentCluster is not out-of-sync with the CurrentCluster number.
         * This could have occurred in append mode, where the file was opened with a filesize % clustersize == 0
         * because of a seek, where the ulAddrCurrentCluster was not updated after extending. This caused the data to
         * be written to the previous cluster(s). */
        if( ( pxFile->ulCurrentCluster == pxFile->ulChainLength - 1 ) &&
            ( pxFile->ulAddrCurrentCluster != pxFile->ulEndOfChain ) )
        {
            pxFile->ulAddrCurrentCluster = pxFile->ulEndOfChain;
        }

        /* By default, 'ffconfigFILE_EXTEND_FLUSHES_BUFFERS' is
         * defined as 1.
         * Users may set it to zero in order to increase the
         * speed of writing to disk. */

        #if ( ffconfigFILE_EXTEND_FLUSHES_BUFFERS != 0 )
        {
            FF_Error_t xTempError;

            xTempError = FF_FlushCache( pxIOManager );

            if( FF_isERR( xError ) == pdFALSE )
            {
                xError = xTempError;
            }
        }
        #endif /* ffconfigFILE_EXTEND_FLUSHES_BUFFERS */

        if( pxFile->ulFilePointer == pxFile->ulFileSize )
        {
            /* Writing at the end of a file, while new clusters have just been added.
             * Make sure that the fields 'ulCurrentCluster' and 'ulAddrCurrentCluster' are
             * set correctly.
             */
            if( ( pxFile->ulValidFlags & FF_VALID_FLAG_EXTENDED ) == 0U )
            {
                pxFile->ulValidFlags |= FF_VALID_FLAG_EXTENDED;
                FF_Error_t xTempError = FF_ERR_NONE;
                uint32_t ulNewCluster = FF_getClusterChainNumber( pxIOManager, pxFile->ulFilePointer, 1 );
                FF_LockFAT( pxIOManager );
                {
                    pxFile->ulAddrCurrentCluster = FF_TraverseFAT( pxIOManager, pxFile->ulObjectCluster, ulNewCluster, &( xTempError ) );
                    pxFile->ulCurrentCluster = ulNewCluster;
                }
                FF_UnlockFAT( pxIOManager );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xError = xTempError;
                }
            }
        }
    } /* if( ulTotalClustersNeeded > pxFile->ulChainLength ) */

    return xError;
} /* FF_ExtendFile() */
/*-----------------------------------------------------------*/

static FF_Error_t FF_WriteClusters( FF_FILE * pxFile,
                                    uint32_t ulCount,
                                    uint8_t * buffer )
{
    uint32_t ulSectors;
    uint32_t ulSequentialClusters = 0;
    uint32_t ulItemLBA;
    FF_Error_t xError = FF_ERR_NONE;

    while( ulCount != 0 )
    {
        if( ulCount > 1U )
        {
            ulSequentialClusters =
                FF_GetSequentialClusters( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster, ulCount - 1, &xError );

            if( FF_isERR( xError ) )
            {
                break;
            }
        }
        else
        {
            /* Handling the last cluster which is a single one. */
        }

        ulSectors = ( ulSequentialClusters + 1 ) * pxFile->pxIOManager->xPartition.ulSectorsPerCluster;
        ulItemLBA = FF_Cluster2LBA( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster );
        ulItemLBA = FF_getRealLBA( pxFile->pxIOManager, ulItemLBA );

        xError = FF_BlockWrite( pxFile->pxIOManager, ulItemLBA, ulSectors, buffer, pdFALSE );

        if( FF_isERR( xError ) )
        {
            break;
        }

        ulCount -= ulSequentialClusters + 1;

        FF_LockFAT( pxFile->pxIOManager );
        {
            pxFile->ulAddrCurrentCluster =
                FF_TraverseFAT( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster, ulSequentialClusters + 1, &xError );
        }
        FF_UnlockFAT( pxFile->pxIOManager );

        if( FF_isERR( xError ) )
        {
            break;
        }

        pxFile->ulCurrentCluster += ( ulSequentialClusters + 1 );
        buffer += ulSectors * pxFile->pxIOManager->usSectorSize;
        ulSequentialClusters = 0;
    }

    return xError;
} /* FF_WriteClusters */
/*-----------------------------------------------------------*/

/**
 *	@brief	Calculate the Logical Block Address (LBA)
 *
 *	@param	pxFile       The file handle
 *
 *	@return	LBA
 *
 *  Must be set:
 *    - pxFile->ulFilePointer        : byte offset in file
 *    - pxFile->ulAddrCurrentCluster : physical cluster on the partition
 **/
static uint32_t FF_FileLBA( FF_FILE * pxFile )
{
    uint32_t ulItemLBA;

    ulItemLBA = FF_Cluster2LBA( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster );
    ulItemLBA += FF_getMajorBlockNumber( pxFile->pxIOManager, pxFile->ulFilePointer, 1 );
    ulItemLBA = FF_getRealLBA( pxFile->pxIOManager, ulItemLBA );
    ulItemLBA += FF_getMinorBlockNumber( pxFile->pxIOManager, pxFile->ulFilePointer, 1 );

    return ulItemLBA;
} /* FF_FileLBA() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Depending on FilePointer, calculate CurrentCluster
 *          and traverse the FAT to find the right ulAddrCurrentCluster
 *
 *	@param	pxFile       The file handle
 *
 *	@return	FF_ERR_NONE on success
 *	@return	Possible error returned by FF_TraverseFAT() or END_OF_DIR
 *
 *  Side effects:
 *    - pxFile->ulCurrentCluster     : relative cluster number (0 <= Num < ulChainLength)
 *    - pxFile->ulAddrCurrentCluster : physical cluster on the partition
 **/
static uint32_t FF_SetCluster( FF_FILE * pxFile,
                               FF_Error_t * pxError )
{
    FF_IOManager_t * pxIOManager = pxFile->pxIOManager;
    uint32_t ulNewCluster = FF_getClusterChainNumber( pxIOManager, pxFile->ulFilePointer, 1 );
    FF_Error_t xResult = FF_ERR_NONE;
    uint32_t ulReturn;

    if( ulNewCluster > pxFile->ulCurrentCluster )
    {
        FF_LockFAT( pxIOManager );
        {
            pxFile->ulAddrCurrentCluster = FF_TraverseFAT( pxIOManager, pxFile->ulAddrCurrentCluster,
                                                           ulNewCluster - pxFile->ulCurrentCluster, &xResult );
        }
        FF_UnlockFAT( pxIOManager );
    }
    else if( ulNewCluster < pxFile->ulCurrentCluster )
    {
        FF_LockFAT( pxIOManager );
        {
            pxFile->ulAddrCurrentCluster = FF_TraverseFAT( pxIOManager, pxFile->ulObjectCluster, ulNewCluster, &xResult );
        }
        FF_UnlockFAT( pxIOManager );
    }
    else
    {
        /* Well positioned. */
    }

    if( FF_isERR( xResult ) == pdFALSE )
    {
        pxFile->ulCurrentCluster = ulNewCluster;
        ulReturn = FF_FileLBA( pxFile );
    }
    else
    {
        ulReturn = 0;
    }

    *pxError = xResult;

    return ulReturn;
} /* FF_SetCluster() */
/*-----------------------------------------------------------*/

static uint32_t FF_ReadPartial( FF_FILE * pxFile,
                                uint32_t ulItemLBA,
                                uint32_t ulRelBlockPos,
                                uint32_t ulCount,
                                uint8_t * pucBuffer,
                                FF_Error_t * pxError )
{
    FF_Error_t xError = FF_ERR_NONE;
    uint32_t ulBytesRead;

    /* Bytes to read are within a block and less than a block size. */
    #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 )
    {
        BaseType_t xLastRead;

        /* Optimised method: each file handle holds one data block
         * in cache: 'pxFile->pucBuffer'. */
        /* See if the current block will be accessed after this read: */
        if( ( ulRelBlockPos + ulCount ) >= ( uint32_t ) pxFile->pxIOManager->usSectorSize )
        {
            /* After this read, ulFilePointer will point to the next block/sector. */
            xLastRead = pdTRUE;
        }
        else
        {
            /* It is not the last read within this block/sector. */
            xLastRead = pdFALSE;
        }

        if( ( pxFile->ucState & FF_BUFSTATE_VALID ) == 0 )
        {
            xError = FF_BlockRead( pxFile->pxIOManager, ulItemLBA, 1, pxFile->pucBuffer, pdFALSE );

            if( FF_isERR( xError ) == pdFALSE )
            {
                pxFile->ucState = FF_BUFSTATE_VALID;
            }
        }

        if( ( pxFile->ucState & FF_BUFSTATE_VALID ) != 0 )
        {
            memcpy( pucBuffer, pxFile->pucBuffer + ulRelBlockPos, ulCount );
            pxFile->ulFilePointer += ulCount;
            ulBytesRead = ulCount;

            if( ( xLastRead == pdTRUE ) && ( ( pxFile->ucState & FF_BUFSTATE_WRITTEN ) != 0 ) )
            {
                /* If the data was changed (file in 'update' mode), store the changes: */
                xError = FF_BlockWrite( pxFile->pxIOManager, ulItemLBA, 1, pxFile->pucBuffer, pdFALSE );
            }
        }
        else
        {
            ulBytesRead = 0ul;
        }

        if( xLastRead == pdTRUE )
        {
            /* As the next FF_Read() will go passed the current block, invalidate the buffer now. */
            pxFile->ucState = FF_BUFSTATE_INVALID;
        }
    }
    #else /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */
    {
        FF_Buffer_t * pxBuffer;
        /* Reading in the standard way, using FF_Buffer_t. */
        pxBuffer = FF_GetBuffer( pxFile->pxIOManager, ulItemLBA, FF_MODE_READ );

        if( pxBuffer == NULL )
        {
            xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_READ );
            ulBytesRead = 0ul;
        }
        else
        {
            memcpy( pucBuffer, pxBuffer->pucBuffer + ulRelBlockPos, ulCount );

            /* Releasing a buffer in FF_MODE_READ mode will not lead to an error,
             * because no disk access is needed. */
            xError = FF_ReleaseBuffer( pxFile->pxIOManager, pxBuffer );
            pxFile->ulFilePointer += ulCount;
            ulBytesRead = ulCount;
        }
    }
    #endif /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */

    *pxError = xError;

    return ulBytesRead;
} /* FF_ReadPartial() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Equivalent to fread()
 *
 *	@param	pxFile			FF_FILE object that was created by FF_Open().
 *	@param	ulElementSize	The size of an element to read.
 *	@param	ulCount			The number of elements to read.
 *	@param	pucBuffer       A pointer to a buffer of adequate size to be filled with the requested data.
 *
 *	@return Number of bytes read.
 *
 * FF_Read() and FF_Write() work very similar. They both complete their task in 5 steps:
 *	1. Read bytes up to a sector border:  FF_ReadPartial()
 *	2. Read sectors up to cluster border: FF_BlockRead()
 *	3. Read complete clusters:            FF_ReadClusters()
 *	4. Read remaining sectors:            FF_BlockRead()
 *	5. Read remaining bytes:              FF_ReadPartial()
 **/
int32_t FF_Read( FF_FILE * pxFile,
                 uint32_t ulElementSize,
                 uint32_t ulCount,
                 uint8_t * pucBuffer )
{
    uint32_t ulBytesLeft = ulElementSize * ulCount;
    uint32_t ulBytesRead = 0;
    uint32_t ulBytesToRead;
    FF_IOManager_t * pxIOManager;
    uint32_t ulRelBlockPos;
    uint32_t ulItemLBA;
    int32_t lResult;
    uint32_t ulSectors;
    uint32_t ulRelClusterPos;
    uint32_t ulBytesPerCluster;
    FF_Error_t xError;

    if( pxFile == NULL )
    {
        xError = FF_createERR( FF_ERR_NULL_POINTER, FF_READ );
    }
    else
    {
        /* Check validity of the handle and the current position within the file. */
        xError = FF_CheckValid( pxFile );

        if( FF_isERR( xError ) == pdFALSE )
        {
            if( ( pxFile->ucMode & FF_MODE_READ ) == 0 )
            {
                /* File was not opened with READ mode access. */
                xError = FF_createERR( FF_ERR_FILE_NOT_OPENED_IN_READ_MODE, FF_READ );
            }
            else if( pxFile->ulFilePointer >= pxFile->ulFileSize )
            {
                /* The end-of-file is reached.  The error READ_ZERO will not be
                 * returned, it is just used to avoid further processing. */
                xError = FF_createERR( FF_ERR_FILE_READ_ZERO, FF_READ );
            }
            else if( ( pxFile->ulFilePointer + ulBytesLeft ) > pxFile->ulFileSize )
            {
                /* Note that many bytes can be read. */
                ulBytesLeft = pxFile->ulFileSize - pxFile->ulFilePointer;
            }
        }
        else
        {
            /* The file handle is not valid. */
        }
    } /* else pxFile != NULL */

    if( FF_isERR( xError ) == pdFALSE )
    {
        pxIOManager = pxFile->pxIOManager;

        /* And calculate the Logical Block Address. */
        ulItemLBA = FF_SetCluster( pxFile, &xError );

        /* Get the position within a block. */
        ulRelBlockPos = FF_getMinorBlockEntry( pxIOManager, pxFile->ulFilePointer, 1 );

        /* Open a do {} while( 0 ) loop to allow easy breaks: */
        do
        {
            if( ( ulRelBlockPos + ulBytesLeft ) <= ( uint32_t ) pxIOManager->usSectorSize )
            {
                /*---------- A small read within the current block only. */
                ulBytesRead = FF_ReadPartial( pxFile, ulItemLBA, ulRelBlockPos, ulBytesLeft, pucBuffer, &xError );
                break;
            }

            /*---------- Read (memcpy) to a Sector Boundary. */
            if( ulRelBlockPos != 0 )
            {
                /* Not on a sector boundary, at this point the LBA is known. */
                ulBytesToRead = pxIOManager->usSectorSize - ulRelBlockPos;
                ulBytesRead = FF_ReadPartial( pxFile, ulItemLBA, ulRelBlockPos, ulBytesToRead, pucBuffer, &xError );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                ulBytesLeft -= ulBytesRead;
                pucBuffer += ulBytesRead;
            }

            /*---------- Read sectors, up to a Cluster Boundary. */
            ulBytesPerCluster = ( pxIOManager->xPartition.ulSectorsPerCluster * pxIOManager->usSectorSize );
            ulRelClusterPos = pxFile->ulFilePointer % ( ulBytesPerCluster * pxIOManager->xPartition.ucBlkFactor );

            if( ( ulRelClusterPos != 0 ) && ( ( ulRelClusterPos + ulBytesLeft ) >= ulBytesPerCluster ) )
            {
                /* Need to get to cluster boundary. */
                ulItemLBA = FF_SetCluster( pxFile, &xError );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                ulSectors = pxIOManager->xPartition.ulSectorsPerCluster - ( ulRelClusterPos / pxIOManager->usSectorSize );
                xError = FF_BlockRead( pxIOManager, ulItemLBA, ulSectors, pucBuffer, pdFALSE );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                ulBytesToRead = ulSectors * pxIOManager->usSectorSize;
                ulBytesLeft -= ulBytesToRead;
                pucBuffer += ulBytesToRead;
                ulBytesRead += ulBytesToRead;
                pxFile->ulFilePointer += ulBytesToRead;
            }

            /*---------- Read entire clusters. */
            if( ulBytesLeft >= ulBytesPerCluster )
            {
                uint32_t ulClusters;

                FF_SetCluster( pxFile, &xError );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                ulClusters = ulBytesLeft / ulBytesPerCluster;

                xError = FF_ReadClusters( pxFile, ulClusters, pucBuffer );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                ulBytesToRead = ulBytesPerCluster * ulClusters;
                pxFile->ulFilePointer += ulBytesToRead;
                ulBytesLeft -= ulBytesToRead;
                pucBuffer += ulBytesToRead;
                ulBytesRead += ulBytesToRead;
            }

            /*---------- Read Remaining Blocks. */
            while( ulBytesLeft >= ( uint32_t ) pxIOManager->usSectorSize )
            {
                ulSectors = ulBytesLeft / pxIOManager->usSectorSize;
                {
                    /* HT: I'd leave these pPart/ulOffset for readability */
                    /* and shorter code lines */
                    FF_Partition_t * pPart = &( pxIOManager->xPartition );
                    uint32_t ulOffset = ( pxFile->ulFilePointer / pxIOManager->usSectorSize ) % pPart->ulSectorsPerCluster;
                    uint32_t ulRemain = pPart->ulSectorsPerCluster - ulOffset;

                    if( ulSectors > ulRemain )
                    {
                        ulSectors = ulRemain;
                    }
                }

                ulItemLBA = FF_SetCluster( pxFile, &xError );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                xError = FF_BlockRead( pxIOManager, ulItemLBA, ulSectors, pucBuffer, pdFALSE );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                ulBytesToRead = ulSectors * pxIOManager->usSectorSize;
                pxFile->ulFilePointer += ulBytesToRead;
                ulBytesLeft -= ulBytesToRead;
                pucBuffer += ulBytesToRead;
                ulBytesRead += ulBytesToRead;
            }

            /*---------- Read (memcpy) Remaining Bytes */
            if( ulBytesLeft == 0 )
            {
                break;
            }

            ulItemLBA = FF_SetCluster( pxFile, &xError );

            if( FF_isERR( xError ) )
            {
                break;
            }

            /* Bytes to read are within a block and less than a block size. */
            FF_ReadPartial( pxFile, ulItemLBA, 0, ulBytesLeft, pucBuffer, &xError );

            if( FF_isERR( xError ) == pdFALSE )
            {
                ulBytesRead += ulBytesLeft;
            }
        }
        while( pdFALSE );
    } /* if( FF_isERR( xError ) == pdFALSE ) */

    if( FF_GETERROR( xError ) == FF_ERR_FILE_READ_ZERO )
    {
        lResult = 0;
    }
    else if( FF_isERR( xError ) )
    {
        lResult = xError;
    }
    else
    {
        lResult = ( int32_t ) ( ulBytesRead / ulElementSize );
    }

    return lResult;
} /* FF_Read() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Equivalent to fgetc()
 *
 *	@param	pxFile		FF_FILE object that was created by FF_Open().
 *
 *	@retval The character that was read (cast as a 32-bit interger). -1 on EOF.
 *	@retval FF_Error_t code. (Check with if(FF_isERR(xRetVal)) {}).
 *	@retval -1 EOF (end of file).
 *
 **/
int32_t FF_GetC( FF_FILE * pxFile )
{
    uint32_t ulItemLBA;
    uint8_t ucReturnedChar;
    uint32_t ulRelBlockPos;
    FF_Error_t xResult;

    if( pxFile == NULL )
    {
        xResult = FF_createERR( FF_ERR_NULL_POINTER, FF_GETC ); /* Ensure this is a signed error. */
    }
    else if( ( pxFile->ucMode & FF_MODE_READ ) == 0 )
    {
        xResult = FF_createERR( FF_ERR_FILE_NOT_OPENED_IN_READ_MODE, FF_GETC );
    }
    else if( pxFile->ulFilePointer >= pxFile->ulFileSize )
    {
        /* The end-of-file is reached.  The error READ_ZERO will not be
         * returned, it is just used to avoid further processing. */
        xResult = FF_createERR( FF_ERR_FILE_READ_ZERO, FF_READ );
    }
    else
    {
        ulRelBlockPos = FF_getMinorBlockEntry( pxFile->pxIOManager, pxFile->ulFilePointer, 1 );

        ulItemLBA = FF_SetCluster( pxFile, &xResult );

        if( FF_isERR( xResult ) == pdFALSE )
        {
            FF_ReadPartial( pxFile, ulItemLBA, ulRelBlockPos, 1, &ucReturnedChar, &xResult );

            if( FF_isERR( xResult ) == pdFALSE )
            {
                xResult = ( int32_t ) ( ( uint32_t ) ucReturnedChar );
            }
        }
    }

    return ( int32_t ) xResult;
} /* FF_GetC() */
/*-----------------------------------------------------------*/

/**
 * @brief	Gets a Line from a Text File, but no more than ulLimit characters. The line will be NULL terminated.
 *
 *			The behaviour of this function is undefined when called on a binary file.
 *			It should just read in ulLimit bytes of binary, and ZERO terminate the line.
 *
 *			This function works for both UNIX line feeds, and Windows CRLF type files.
 *
 * @param	pxFile	The FF_FILE object pointer.
 * @param	pcLine	The character buffer where the line should be stored.
 * @param	ulLimit	This should be the max number of characters that szLine can hold.
 *
 * @return	The number of characters read from the line, on success.
 *          0 when no more lines are available, or when ulLimit is 0.
 *          FF_ERR_NULL_POINTER if pxFile or szLine are NULL;
 *
 **/
int32_t FF_GetLine( FF_FILE * pxFile,
                    char * pcLine,
                    uint32_t ulLimit )
{
    int32_t iChar = 0;
    BaseType_t xIndex;
    FF_Error_t xResult = FF_ERR_NONE;

    if( ( pxFile == NULL ) || ( pcLine == NULL ) )
    {
        xResult = FF_createERR( FF_ERR_NULL_POINTER, FF_GETLINE );
    }
    else
    {
        for( xIndex = 0; xIndex < ( BaseType_t ) ( ulLimit - 1 ); ++xIndex )
        {
            iChar = FF_GetC( pxFile );

            if( FF_isERR( iChar ) == pdFALSE )
            {
                pcLine[ xIndex ] = ( char ) iChar;

                if( iChar == '\n' )
                {
                    /* Read until the first linefeed.  Move xIndex forward so the
                     * null terminator does not overwrite the \n.  xIndex must be less
                     * thank ( ulLimit - 1 ), so incrementing it here cannot make it
                     * greater than ulLimit - 1, so the NULL can be inserted without
                     * overflowing the buffer. */
                    xIndex++;
                    break;
                }
            }
            else
            {
                if( ( FF_GETERROR( iChar ) == FF_ERR_FILE_READ_ZERO ) && ( xIndex > 0 ) )
                {
                    /* Although FF_GetC() returns an End Of File,
                     * the last few characters will be returned first. */
                    iChar = ( int32_t ) xIndex;
                }

                break;
            }
        }

        /* Make sure that the resulting string always ends with a zero: */
        pcLine[ xIndex ] = '\0';

        /*_RB_ In some paths this will be the second time FF_isERR() is called
         * on the same value. */
        if( FF_isERR( iChar ) == pdFALSE )
        {
            /* Return the number of bytes read. */
            xResult = ( int32_t ) xIndex;
        }
        else
        {
            /* Return iChar as an error code (see FF_GetC()). */
            xResult = iChar;
        }
    }

    return xResult;
} /* FF_GetLine() */
/*-----------------------------------------------------------*/

static uint32_t FF_WritePartial( FF_FILE * pxFile,
                                 uint32_t ulItemLBA,
                                 uint32_t ulRelBlockPos,
                                 uint32_t ulCount,
                                 const uint8_t * pucBuffer,
                                 FF_Error_t * pxError )
{
    FF_Error_t xError;
    uint32_t ulBytesWritten;

    #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 )
    {
        BaseType_t xLastRead;

        if( ( ulRelBlockPos + ulCount ) >= ( uint32_t ) pxFile->pxIOManager->usSectorSize )
        {
            /* After this read, ulFilePointer will point to the next block/sector. */
            xLastRead = pdTRUE;
        }
        else
        {
            /* It is not the last read within this block/sector. */
            xLastRead = pdFALSE;
        }

        if( ( ( pxFile->ucState & FF_BUFSTATE_VALID ) == 0 ) &&
            ( ( ulRelBlockPos != 0 ) || ( pxFile->ulFilePointer < pxFile->ulFileSize ) ) )
        {
            xError = FF_BlockRead( pxFile->pxIOManager, ulItemLBA, 1, pxFile->pucBuffer, pdFALSE );
            /* pxFile->ucState will be set later on. */
        }
        else
        {
            xError = FF_ERR_NONE;

            /* the buffer is valid or a whole block/sector will be written, so it is
             * not necessary to read the contents first. */
        }

        if( FF_isERR( xError ) == pdFALSE )
        {
            memcpy( pxFile->pucBuffer + ulRelBlockPos, pucBuffer, ulCount );

            if( xLastRead == pdTRUE )
            {
                xError = FF_BlockWrite( pxFile->pxIOManager, ulItemLBA, 1, pxFile->pucBuffer, pdFALSE );
                pxFile->ucState = FF_BUFSTATE_INVALID;
            }
            else
            {
                pxFile->ucState |= FF_BUFSTATE_WRITTEN | FF_BUFSTATE_VALID;
            }
        }
        else
        {
            pxFile->ucState = FF_BUFSTATE_INVALID;
        }
    }
    #else /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */
    {
        FF_Buffer_t * pxBuffer;

        if( ( ulRelBlockPos == 0 ) && ( pxFile->ulFilePointer >= pxFile->ulFileSize ) )
        {
            /* An entire sector will be written. */
            pxBuffer = FF_GetBuffer( pxFile->pxIOManager, ulItemLBA, FF_MODE_WR_ONLY );
        }
        else
        {
            /* A partial write will be done, make sure to read the contents before
             * changing anything. */
            pxBuffer = FF_GetBuffer( pxFile->pxIOManager, ulItemLBA, FF_MODE_WRITE );
        }

        if( pxBuffer == NULL )
        {
            xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_WRITE );
        }
        else
        {
            /* Here we copy to the sector boundary. */
            memcpy( ( pxBuffer->pucBuffer + ulRelBlockPos ), pucBuffer, ulCount );

            xError = FF_ReleaseBuffer( pxFile->pxIOManager, pxBuffer );
        }
    }
    #endif /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */

    if( FF_isERR( xError ) == pdFALSE )
    {
        pxFile->ulFilePointer += ulCount;
        ulBytesWritten = ulCount;

        if( pxFile->ulFilePointer > pxFile->ulFileSize )
        {
            pxFile->ulFileSize = pxFile->ulFilePointer;
        }
    }
    else
    {
        ulBytesWritten = 0ul;
    }

    *pxError = xError;

    return ulBytesWritten;
} /* FF_WritePartial() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Writes data to a File.
 *
 *	@param	pxFile			FILE Pointer.
 *	@param	ulElementSize		Size of an Element of Data to be copied. (in bytes).
 *	@param	ulCount			Number of Elements of Data to be copied. (ulElementSize * ulCount must not exceed ((2^31)-1) bytes. (2GB). For best performance, multiples of 512 bytes or Cluster sizes are best.
 *	@param	pucBuffer			Byte-wise pucBuffer containing the data to be written.
 *
 * FF_Read() and FF_Write() work very similar. They both complete their task in 5 steps:
 *	1. Write bytes up to a sector border:  FF_WritePartial()
 *	2. Write sectors up to cluster border: FF_BlockWrite()
 *	3. Write complete clusters:            FF_WriteClusters()
 *	4. Write remaining sectors:            FF_BlockWrite()
 *	5. Write remaining bytes:              FF_WritePartial()
 *	@return FF_ERR_NONE when success, otherwise one of FF_ERR_* when failure
 **/
int32_t FF_Write( FF_FILE * pxFile,
                  uint32_t ulElementSize,
                  uint32_t ulCount,
                  uint8_t * pucBuffer )
{
    uint32_t ulBytesLeft = ulElementSize * ulCount;
    uint32_t nBytesWritten = 0;
    uint32_t nBytesToWrite;
    FF_IOManager_t * pxIOManager;
    uint32_t ulRelBlockPos;
    uint32_t ulItemLBA;
    int32_t lResult;
    uint32_t ulSectors;
    uint32_t ulRelClusterPos;
    uint32_t ulBytesPerCluster;
    FF_Error_t xError;

    if( pxFile == NULL )
    {
        xError = FF_createERR( FF_ERR_NULL_POINTER, FF_READ );
    }
    else
    {
        /* Check validity of the handle and the current position within the file. */
        xError = FF_CheckValid( pxFile );

        if( FF_isERR( xError ) == pdFALSE )
        {
            if( ( pxFile->ucMode & FF_MODE_WRITE ) == 0 )
            {
                xError = FF_createERR( FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE, FF_WRITE );
            }
            /* Make sure a write is after the append point. */
            else if( ( pxFile->ucMode & FF_MODE_APPEND ) != 0 )
            {
                if( pxFile->ulFilePointer < pxFile->ulFileSize )
                {
                    xError = FF_Seek( pxFile, 0, FF_SEEK_END );
                }
            }
        }
    }

    if( FF_isERR( xError ) == pdFALSE )
    {
        pxIOManager = pxFile->pxIOManager;

        /* Open a do{} while( 0 ) loop to allow the use of breaks */
        do
        {
            /* Extend File for at least ulBytesLeft!
             * Handle file-space allocation
             + 1 byte because the code assumes there is always a next cluster */
            xError = FF_ExtendFile( pxFile, pxFile->ulFilePointer + ulBytesLeft + 1 );

            if( FF_isERR( xError ) )
            {
                /* On every error, break from the while( 0 ) loop. */
                break;
            }

            ulRelBlockPos = FF_getMinorBlockEntry( pxIOManager, pxFile->ulFilePointer, 1 ); /* Get the position within a block. */
            ulItemLBA = FF_SetCluster( pxFile, &xError );

            if( FF_isERR( xError ) )
            {
                break;
            }

            if( ( ulRelBlockPos + ulBytesLeft ) <= ( uint32_t ) pxIOManager->usSectorSize )
            {
                /* Bytes to write are within a block and and do not go passed the current block. */
                nBytesWritten = FF_WritePartial( pxFile, ulItemLBA, ulRelBlockPos, ulBytesLeft, pucBuffer, &xError );
                break;
            }

            /*---------- Write (memcpy) to a Sector Boundary. */
            if( ulRelBlockPos != 0 )
            {
                /* Not writing on a sector boundary, at this point the LBA is known. */
                nBytesToWrite = pxIOManager->usSectorSize - ulRelBlockPos;
                nBytesWritten = FF_WritePartial( pxFile, ulItemLBA, ulRelBlockPos, nBytesToWrite, pucBuffer, &xError );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                ulBytesLeft -= nBytesWritten;
                pucBuffer += nBytesWritten;
            }

            /*---------- Write sectors, up to a Cluster Boundary. */
            ulBytesPerCluster = ( pxIOManager->xPartition.ulSectorsPerCluster * pxIOManager->usSectorSize );
            ulRelClusterPos = FF_getClusterPosition( pxIOManager, pxFile->ulFilePointer, 1 );

            if( ( ulRelClusterPos != 0 ) && ( ( ulRelClusterPos + ulBytesLeft ) >= ulBytesPerCluster ) )
            {
                /* Need to get to cluster boundary */
                ulItemLBA = FF_SetCluster( pxFile, &xError );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                ulSectors = pxIOManager->xPartition.ulSectorsPerCluster - ( ulRelClusterPos / pxIOManager->usSectorSize );
                xError = FF_BlockWrite( pxIOManager, ulItemLBA, ulSectors, pucBuffer, pdFALSE );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                nBytesToWrite = ulSectors * pxIOManager->usSectorSize;
                ulBytesLeft -= nBytesToWrite;
                pucBuffer += nBytesToWrite;
                nBytesWritten += nBytesToWrite;
                pxFile->ulFilePointer += nBytesToWrite;

                if( pxFile->ulFilePointer > pxFile->ulFileSize )
                {
                    pxFile->ulFileSize = pxFile->ulFilePointer;
                }
            }

            /*---------- Write entire Clusters. */
            if( ulBytesLeft >= ulBytesPerCluster )
            {
                uint32_t ulClusters;

                FF_SetCluster( pxFile, &xError );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                ulClusters = ( ulBytesLeft / ulBytesPerCluster );

                xError = FF_WriteClusters( pxFile, ulClusters, pucBuffer );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                nBytesToWrite = ulBytesPerCluster * ulClusters;
                ulBytesLeft -= nBytesToWrite;
                pucBuffer += nBytesToWrite;
                nBytesWritten += nBytesToWrite;
                pxFile->ulFilePointer += nBytesToWrite;

                if( pxFile->ulFilePointer > pxFile->ulFileSize )
                {
                    pxFile->ulFileSize = pxFile->ulFilePointer;
                }
            }

            /*---------- Write Remaining Blocks */
            while( ulBytesLeft >= ( uint32_t ) pxIOManager->usSectorSize )
            {
                ulSectors = ulBytesLeft / pxIOManager->usSectorSize;
                {
                    /* HT: I'd leave these pPart/ulOffset for readability... */
                    FF_Partition_t * pPart = &( pxIOManager->xPartition );
                    uint32_t ulOffset = ( pxFile->ulFilePointer / pxIOManager->usSectorSize ) % pPart->ulSectorsPerCluster;
                    uint32_t ulRemain = pPart->ulSectorsPerCluster - ulOffset;

                    if( ulSectors > ulRemain )
                    {
                        ulSectors = ulRemain;
                    }
                }

                ulItemLBA = FF_SetCluster( pxFile, &xError );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                xError = FF_BlockWrite( pxIOManager, ulItemLBA, ulSectors, pucBuffer, pdFALSE );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                nBytesToWrite = ulSectors * pxIOManager->usSectorSize;
                ulBytesLeft -= nBytesToWrite;
                pucBuffer += nBytesToWrite;
                nBytesWritten += nBytesToWrite;
                pxFile->ulFilePointer += nBytesToWrite;

                if( pxFile->ulFilePointer > pxFile->ulFileSize )
                {
                    pxFile->ulFileSize = pxFile->ulFilePointer;
                }
            }

            /*---------- Write (memcpy) Remaining Bytes */
            if( ulBytesLeft == 0 )
            {
                break;
            }

            ulItemLBA = FF_SetCluster( pxFile, &xError );

            if( FF_isERR( xError ) )
            {
                break;
            }

            FF_WritePartial( pxFile, ulItemLBA, 0, ulBytesLeft, pucBuffer, &xError );
            nBytesWritten += ulBytesLeft;
        }
        while( pdFALSE );
    }

    if( FF_isERR( xError ) )
    {
        lResult = xError;
    }
    else
    {
        lResult = ( int32_t ) ( nBytesWritten / ulElementSize );
    }

    return lResult;
} /* FF_Write() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Writes a char to a FILE.
 *
 *	@param	pxFile		FILE Pointer.
 *	@param	ucValue	Char to be placed in the file.
 *
 *	@return	Returns the value written to the file, or a value less than 0.
 *
 **/
int32_t FF_PutC( FF_FILE * pxFile,
                 uint8_t ucValue )
{
    uint32_t ulItemLBA;
    uint32_t ulRelBlockPos;
    FF_Error_t xResult;

    if( pxFile == NULL )
    { /* Ensure we don't have a Null file pointer on a Public interface. */
        xResult = FF_createERR( FF_ERR_NULL_POINTER, FF_PUTC );
    }
    else if( ( pxFile->ucMode & FF_MODE_WRITE ) == 0 )
    {
        xResult = FF_createERR( FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE, FF_PUTC );
    }
    else
    {
        xResult = FF_ERR_NONE;

        do
        {
            /* Make sure a write is after the append point. */
            if( ( pxFile->ucMode & FF_MODE_APPEND ) != 0 )
            {
                if( pxFile->ulFilePointer < pxFile->ulFileSize )
                {
                    xResult = FF_Seek( pxFile, 0, FF_SEEK_END );

                    if( FF_isERR( xResult ) )
                    {
                        break;
                    }
                }
            }

            ulRelBlockPos = FF_getMinorBlockEntry( pxFile->pxIOManager, pxFile->ulFilePointer, 1 );

            /* Handle File Space Allocation. */
            /* We'll write 1 byte and always have a next cluster reserved. */
            xResult = FF_ExtendFile( pxFile, pxFile->ulFilePointer + 2 );

            if( FF_isERR( xResult ) )
            {
                break;
            }

            ulItemLBA = FF_SetCluster( pxFile, &xResult );

            if( FF_isERR( xResult ) )
            {
                break;
            }

            FF_WritePartial( pxFile, ulItemLBA, ulRelBlockPos, 1, &ucValue, &xResult );

            if( FF_isERR( xResult ) == pdFALSE )
            {
                xResult = ( FF_Error_t ) ucValue;
            }
        } while( pdFALSE );
    }

    return xResult;
} /* FF_PutC() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Equivalent to fseek()
 *
 *	@param	pxFile		FF_FILE object that was created by FF_Open().
 *	@param	lOffset	    An integer (+/-) to seek to, from the specified origin.
 *	@param	xOrigin		Where to seek from. (FF_SEEK_SET seek from start, FF_SEEK_CUR seek from current position, or FF_SEEK_END seek from end of file).
 *
 *	@retval 0 on Success,
 *	@retval -2 if offset results in an invalid position in the file.
 *	@retval FF_ERR_NULL_POINTER if a FF_FILE pointer was not received.
 *	@retval -3 if an invalid origin was provided.
 *
 **/
FF_Error_t FF_Seek( FF_FILE * pxFile,
                    int32_t lOffset,
                    BaseType_t xOrigin )
{
    FF_Error_t xError;
    uint32_t ulPosition = 0ul;

    xError = FF_CheckValid( pxFile );

    if( FF_isERR( xError ) == pdFALSE )
    {
        xError = FF_FlushCache( pxFile->pxIOManager );

        #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 )
        {
            if( FF_isERR( xError ) == pdFALSE )
            {
                /* Here we must ensure that if the user tries to seek, and we had data in the file's
                 * write buffer that this is written to disk. */
                if( ( pxFile->ucState & FF_BUFSTATE_WRITTEN ) != 0 )
                {
                    xError = FF_BlockWrite( pxFile->pxIOManager, FF_FileLBA( pxFile ), 1, pxFile->pucBuffer, pdFALSE );
                }

                pxFile->ucState = FF_BUFSTATE_INVALID;
            }
        }
        #endif /* ffconfigOPTIMISE_UNALIGNED_ACCESS */

        if( FF_isERR( xError ) == pdFALSE )
        {
            if( xOrigin == FF_SEEK_SET )
            {
                ulPosition = ( uint32_t ) lOffset;
            }
            else if( xOrigin == FF_SEEK_CUR )
            {
                if( lOffset >= ( int32_t ) 0 )
                {
                    ulPosition = pxFile->ulFilePointer + ( ( uint32_t ) lOffset );
                }
                else
                {
                    ulPosition = pxFile->ulFilePointer - ( ( uint32_t ) ( -lOffset ) );
                }
            }
            else if( xOrigin == FF_SEEK_END )
            {
                /* 'FF_SEEK_END' only allows zero or negative values. */
                if( lOffset <= ( int32_t ) 0 )
                {
                    ulPosition = pxFile->ulFileSize - ( ( uint32_t ) ( -lOffset ) );
                }
            }
            else
            {
                xError = FF_createERR( FF_ERR_FILE_SEEK_INVALID_ORIGIN, FF_SEEK );
                /* To suppress a compiler warning. */
                ulPosition = ( uint32_t ) 0u;
            }

            if( FF_isERR( xError ) == pdFALSE )
            {
                if( ulPosition <= ( uint32_t ) pxFile->ulFileSize )
                {
                    if( ulPosition != ( uint32_t ) pxFile->ulFilePointer )
                    {
                        pxFile->ulFilePointer = ulPosition;
                        FF_SetCluster( pxFile, &xError );
                    }
                }
                else
                {
                    xError = FF_createERR( FF_ERR_FILE_SEEK_INVALID_POSITION, FF_SEEK );
                }
            }
        }
    }

    return xError;
} /* FF_Seek() */
/*-----------------------------------------------------------*/

#if ( ffconfigREMOVABLE_MEDIA != 0 )

/**
 *	@brief	Invalidate all file handles belonging to pxIOManager
 *
 *	@param	pIoMan		FF_IOManager_t object that was created by FF_CreateIOManager().
 *
 *	@retval 0 if no handles were open
 *	@retval >0 the amount of handles that were invalidated
 *	@retval <0 probably an invalid FF_IOManager_t pointer
 *
 **/
    int32_t FF_Invalidate( FF_IOManager_t * pxIOManager )
    {
        int32_t xResult;
        FF_FILE * pxFileChain;

        if( pxIOManager == NULL )
        {
            xResult = FF_ERR_NULL_POINTER | FF_INVALIDATE;
        }
        else
        {
            xResult = 0;
            FF_PendSemaphore( pxIOManager->pvSemaphore );
            {
                pxIOManager->ucFlags |= FF_IOMAN_DEVICE_IS_EXTRACTED;
                /* Semaphore is required, or linked list might change */
                pxFileChain = ( FF_FILE * ) pxIOManager->FirstFile;

                if( pxFileChain != NULL )
                {
                    /* Count elements in FirstFile */
                    do
                    {
                        pxFileChain->ulValidFlags |= FF_VALID_FLAG_INVALID;
                        xResult++;
                        pxFileChain = pxFileChain->pxNext;
                    }
                    while( pxFileChain != NULL );
                }
            }

            FF_ReleaseSemaphore( pxIOManager->pvSemaphore );
        }

        return xResult;
    } /* FF_Invalidate() */
#endif /* ffconfigREMOVABLE_MEDIA */
/*-----------------------------------------------------------*/

/**
 *	@brief	Check validity of file handle
 *
 *	@param	pxFile		FF_FILE object that was created by FF_Open().
 *
 *	@retval 0 on success.
 *	@retval FF_ERR_NULL_POINTER       if a null pointer was provided.
 *	@retval FF_ERR_FILE_BAD_HANDLE    if handle is not recognized
 *	@retval FF_ERR_FILE_MEDIA_REMOVED please call FF_Close
 *
 **/
FF_Error_t FF_CheckValid( FF_FILE * pxFile )
{
    FF_FILE * pxFileChain;
    FF_Error_t xError;

    if( ( pxFile == NULL ) || ( pxFile->pxIOManager == NULL ) )
    {
        xError = FF_createERR( FF_ERR_NULL_POINTER, FF_CHECKVALID );
    }
    else
    {
        FF_PendSemaphore( pxFile->pxIOManager->pvSemaphore );
        {
            pxFileChain = ( FF_FILE * ) pxFile->pxIOManager->FirstFile;
            xError = FF_createERR( FF_ERR_FILE_BAD_HANDLE, FF_CHECKVALID );

            while( pxFileChain != NULL )
            {
                if( pxFileChain == pxFile )
                {
                    #if ( ffconfigREMOVABLE_MEDIA != 0 )
                        if( ( pxFileChain->ulValidFlags & FF_VALID_FLAG_INVALID ) != 0 )
                        {
                            /* The medium has been removed while this file handle was open. */
                            xError = FF_createERR( FF_ERR_FILE_MEDIA_REMOVED, FF_CHECKVALID );
                        }
                        else
                    #endif
                    {
                        /* Found the handle, so it is a valid / existing handle. */
                        xError = FF_ERR_NONE;
                    }

                    break;
                }

                pxFileChain = pxFileChain->pxNext;
            }
        }
        FF_ReleaseSemaphore( pxFile->pxIOManager->pvSemaphore );
    }

    return xError;
} /* FF_CheckValid() */
/*-----------------------------------------------------------*/

#if ( ffconfigTIME_SUPPORT != 0 )

/**
 *	@brief	Set the time-stamp(s) of a file entry
 *
 *	@param	pxFile		FF_FILE object that was created by FF_Open().
 *	@param	pxTime      FF_SystemTime_t the time stamp
 *	@param	uxWhat       UBaseType_t a combination of enum ETimeMask
 *
 *	@return 0 or FF_Error_t
 *
 **/
    FF_Error_t FF_SetFileTime( FF_FILE * pxFile,
                               FF_SystemTime_t * pxTime,
                               UBaseType_t uxWhat )
    {
        FF_DirEnt_t xOriginalEntry;
        FF_Error_t xError;

        xError = FF_CheckValid( pxFile );

        if( FF_isERR( xError ) == pdFALSE )
        {
            if( pxFile->ulValidFlags & FF_VALID_FLAG_DELETED )
            { /*if (pxFile->FileDeleted) */
                xError = FF_createERR( FF_ERR_FILE_NOT_FOUND, FF_SETFILETIME );
            }
            else if( ( pxFile->ucMode & ( FF_MODE_WRITE | FF_MODE_APPEND ) ) == 0 )
            {
                xError = FF_createERR( FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE, FF_SETFILETIME );
            }
            else
            {
                /* Update the Dirent! */
                xError = FF_GetEntry( pxFile->pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    if( uxWhat & ETimeCreate )
                    {
                        xOriginalEntry.xCreateTime = *pxTime; /*/< Date and Time Created. */
                    }

                    if( uxWhat & ETimeMod )
                    {
                        xOriginalEntry.xModifiedTime = *pxTime; /*/< Date and Time Modified. */
                    }

                    if( uxWhat & ETimeAccess )
                    {
                        xOriginalEntry.xAccessedTime = *pxTime; /*/< Date of Last Access. */
                    }

                    xError = FF_PutEntry( pxFile->pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry, NULL );
                }

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xError = FF_FlushCache( pxFile->pxIOManager ); /* Ensure all modifed blocks are flushed to disk! */
                }
            }
        }

        return xError;
    } /* FF_SetFileTime() */
#endif /* ffconfigTIME_SUPPORT */
/*-----------------------------------------------------------*/

#if ( ffconfigTIME_SUPPORT != 0 )

/**
 *	@brief	Set the time-stamp(s) of a file entry (by name)
 *
 *	@param	pxIOManager		FF_IOManager_t device handle
 *	@param	pcPath		int8_t/FF_T_WCHAR name of the file
 *	@param	pxTime       FF_SystemTime_t the time stamp
 *	@param	uxWhat       UBaseType_t a combination of enum ETimeMask
 *
 *	@return 0 or FF_Error_t
 *
 **/
    #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
        FF_Error_t FF_SetTime( FF_IOManager_t * pxIOManager,
                               const FF_T_WCHAR * pcPath,
                               FF_SystemTime_t * pxTime,
                               UBaseType_t uxWhat )
    #else
        FF_Error_t FF_SetTime( FF_IOManager_t * pxIOManager,
                               const char * pcPath,
                               FF_SystemTime_t * pxTime,
                               UBaseType_t uxWhat )
    #endif /* ffconfigUNICODE_UTF16_SUPPORT */
    {
        FF_DirEnt_t xOriginalEntry;
        FF_Error_t xError;
        uint32_t ulFileCluster;
        BaseType_t xIndex;
        FF_FindParams_t xFindParams;

        #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
            FF_T_WCHAR pcFileName[ ffconfigMAX_FILENAME ];
        #else
            char pcFileName[ ffconfigMAX_FILENAME ];
        #endif /* ffconfigUNICODE_UTF16_SUPPORT */

        xIndex = ( BaseType_t ) STRLEN( pcPath );

        memset( &xFindParams, '\0', sizeof( xFindParams ) );

        while( xIndex != 0 )
        {
            if( ( pcPath[ xIndex ] == '\\' ) || ( pcPath[ xIndex ] == '/' ) )
            {
                break;
            }

            xIndex--;
        }

        STRNCPY( pcFileName, ( pcPath + xIndex + 1 ), ffconfigMAX_FILENAME - 1 );
        pcFileName[ ffconfigMAX_FILENAME - 1 ] = 0;

        if( xIndex == 0 )
        {
            xIndex = 1;
        }

        xFindParams.ulDirCluster = FF_FindDir( pxIOManager, pcPath, ( uint16_t ) xIndex, &xError );

        if( FF_isERR( xError ) == pdFALSE )
        {
            if( xFindParams.ulDirCluster == 0 )
            {
                xError = FF_createERR( FF_ERR_FILE_NOT_FOUND, FF_SETTIME );
            }
            else
            {
                ulFileCluster = FF_FindEntryInDir( pxIOManager, &xFindParams, pcFileName, 0, &xOriginalEntry, &xError );

                if( ( FF_isERR( xError ) == pdFALSE ) || ( FF_GETERROR( xError ) == FF_ERR_DIR_END_OF_DIR ) )
                {
                    if( ulFileCluster == 0ul )
                    {
                        /*FF_PRINTF ("FF_SetTime: Can not find '%s'\n", pcFileName); */
                        xError = FF_createERR( FF_ERR_FILE_NOT_FOUND, FF_SETTIME );
                    }
                }
            }
        }

        if( FF_isERR( xError ) == pdFALSE )
        {
            /* Update the Dirent! */
            if( uxWhat & ETimeCreate )
            {
                xOriginalEntry.xCreateTime = *pxTime;           /*/< Date and Time Created. */
            }

            if( uxWhat & ETimeMod )
            {
                xOriginalEntry.xModifiedTime = *pxTime;     /*/< Date and Time Modified. */
            }

            if( uxWhat & ETimeAccess )
            {
                xOriginalEntry.xAccessedTime = *pxTime;     /*/< Date of Last Access. */
            }

            xError = FF_PutEntry( pxIOManager, xOriginalEntry.usCurrentItem - 1, xFindParams.ulDirCluster, &xOriginalEntry, NULL );

            if( FF_isERR( xError ) == pdFALSE )
            {
                xError = FF_FlushCache( pxIOManager );          /* Ensure all modified blocks are flushed to disk! */
            }
        }

        return xError;
    }   /* FF_SetTime() */
#endif /* ffconfigTIME_SUPPORT */
/*-----------------------------------------------------------*/

/* *INDENT-OFF* */
#if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
    FF_Error_t FF_SetPerm( FF_IOManager_t * pxIOManager,
                           const FF_T_WCHAR * pcPath,
                           UBaseType_t aPerm )
#else
    FF_Error_t FF_SetPerm( FF_IOManager_t * pxIOManager,
                           const char * pcPath,
                           UBaseType_t aPerm )
#endif
/* *INDENT-ON* */
{
    FF_DirEnt_t xOriginalEntry;
    FF_Error_t xError;
    uint32_t ulFileCluster;
    BaseType_t xIndex;

    #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
        FF_T_WCHAR pcFileName[ ffconfigMAX_FILENAME ];
    #else
        char pcFileName[ ffconfigMAX_FILENAME ];
    #endif
    FF_FindParams_t xFindParams;

    xIndex = ( BaseType_t ) STRLEN( pcPath );

    memset( &xFindParams, '\0', sizeof( xFindParams ) );

    while( xIndex != 0 )
    {
        if( ( pcPath[ xIndex ] == '\\' ) || ( pcPath[ xIndex ] == '/' ) )
        {
            break;
        }

        xIndex--;
    }

    STRNCPY( pcFileName, ( pcPath + xIndex + 1 ), ffconfigMAX_FILENAME - 1 );
    pcFileName[ ffconfigMAX_FILENAME - 1 ] = 0;

    if( xIndex == 0 )
    {
        xIndex = 1;
    }

    /* Open a do {} while( pdFALSE ) loop to allow the use of break statements. */
    do
    {
        xFindParams.ulDirCluster = FF_FindDir( pxIOManager, pcPath, ( uint16_t ) xIndex, &xError );

        if( xError )
        {
            break;
        }

        if( !xFindParams.ulDirCluster )
        {
            xError = FF_createERR( FF_ERR_FILE_NOT_FOUND, FF_SETTIME );
            break;
        }

        ulFileCluster = FF_FindEntryInDir( pxIOManager, &xFindParams, pcFileName, 0, &xOriginalEntry, &xError );

        if( FF_isERR( xError ) )
        {
            break;
        }

        if( ulFileCluster == 0ul )
        {
            /*FF_PRINTF ("FF_SetTime: Can not find '%s'\n", pcFileName); */
            xError = FF_createERR( FF_ERR_FILE_NOT_FOUND, FF_SETTIME );
            break;
        }

        /*	#define FF_FAT_ATTR_READONLY		0x01 */
        /*	#define FF_FAT_ATTR_HIDDEN			0x02 */
        /*	#define FF_FAT_ATTR_SYSTEM			0x04 */
        /*	#define FF_FAT_ATTR_VOLID			0x08 */
        /*	#define FF_FAT_ATTR_DIR				0x10 */
        /*	#define FF_FAT_ATTR_ARCHIVE			0x20 */
        /*	#define FF_FAT_ATTR_LFN				0x0F */
#define FF_FAT_ATTR_USER    ( ( uint8_t ) FF_FAT_ATTR_READONLY | FF_FAT_ATTR_HIDDEN | FF_FAT_ATTR_SYSTEM | FF_FAT_ATTR_ARCHIVE )
        /* Update the Dirent! */
        xOriginalEntry.ucAttrib &= ~FF_FAT_ATTR_USER;
        xOriginalEntry.ucAttrib |= ( aPerm & FF_FAT_ATTR_USER );
        xError = FF_PutEntry( pxIOManager, xOriginalEntry.usCurrentItem - 1, xFindParams.ulDirCluster, &xOriginalEntry, NULL );

        if( FF_isERR( xError ) == pdFALSE )
        {
            xError = FF_FlushCache( pxIOManager ); /* Ensure all modifed blocks are flushed to disk! */
        }
    }
    while( pdFALSE );

    return xError;
} /* FF_SetPerm() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Equivalent to fclose()
 *
 *	@param	pxFile		FF_FILE object that was created by FF_Open().
 *
 *	@retval 0 on success.
 *	@retval -1 if a null pointer was provided.
 *
 **/
FF_Error_t FF_Close( FF_FILE * pxFile )
{
    FF_FILE * pxFileChain;
    FF_DirEnt_t xOriginalEntry;
    FF_Error_t xError;

    /* Opening a do {} while( 0 )  loop to allow the use of the break statement. */
    do
    {
        if( pxFile == NULL )
        {
            xError = FF_createERR( FF_ERR_NULL_POINTER, FF_CLOSE );
            break;
        }

        /* It is important to check that user doesn't supply invalid
         * handle or a handle invalid because of "media removed" */
        xError = FF_CheckValid( pxFile );

        #if ( ffconfigREMOVABLE_MEDIA != 0 )
        {
            if( FF_GETERROR( xError ) == FF_ERR_FILE_MEDIA_REMOVED )
            {
                FF_PendSemaphore( pxFile->pxIOManager->pvSemaphore );
                {
                    pxFileChain = ( FF_FILE * ) pxFile->pxIOManager->FirstFile;

                    if( pxFileChain == pxFile )
                    {
                        pxFile->pxIOManager->FirstFile = pxFile->pxNext;
                    }
                    else
                    {
                        while( pxFileChain )
                        {
                            if( pxFileChain->pxNext == pxFile )
                            {
                                pxFileChain->pxNext = pxFile->pxNext;
                                break;
                            }

                            pxFileChain = pxFileChain->pxNext; /* Forgot this one */
                        }
                    }
                } /* Semaphore released, linked list was shortened! */

                FF_ReleaseSemaphore( pxFile->pxIOManager->pvSemaphore );
                #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 )
                {
                    ffconfigFREE( pxFile->pucBuffer );
                }
                #endif /* ffconfigOPTIMISE_UNALIGNED_ACCESS */
                ffconfigFREE( pxFile ); /* So at least we have freed the pointer. */
                xError = FF_ERR_NONE;
                break;
            }
        }
        #endif /* ffconfigREMOVABLE_MEDIA */

        if( FF_isERR( xError ) )
        {
            /* FF_ERR_FILE_BAD_HANDLE or FF_ERR_NULL_POINTER */
            break;
        }

        /* So here we have a normal valid file handle. */

        /* Sometimes FreeRTOS+FAT will leave a trailing cluster on the end of a cluster chain.
         * To ensure we're compliant we shall now check for this condition and truncate it. */
        if( ( ( pxFile->ulValidFlags & FF_VALID_FLAG_DELETED ) == 0 ) &&
            ( ( pxFile->ucMode & ( FF_MODE_WRITE | FF_MODE_APPEND | FF_MODE_CREATE ) ) != 0 ) )
        {
            uint32_t ulClusterSize;

            /* File is not deleted and it was opened for writing or updating */
            ulClusterSize = pxFile->pxIOManager->xPartition.usBlkSize * pxFile->pxIOManager->xPartition.ulSectorsPerCluster;

            if( ( ( pxFile->ulFileSize % ulClusterSize ) == 0 ) && ( pxFile->ulObjectCluster != 0ul ) )
            {
                /* The file's length is a multiple of cluster size.  This means
                 * that an extra cluster has been reserved, which wasn't necessary. */
                xError = FF_Truncate( pxFile, pdTRUE );
            }

            /* Get the directory entry and update it to show the new file size */
            if( FF_isERR( xError ) == pdFALSE )
            {
                xError = FF_GetEntry( pxFile->pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry );

                /* Now update the directory entry */
                if( ( FF_isERR( xError ) == pdFALSE ) &&
                    ( ( pxFile->ulFileSize != xOriginalEntry.ulFileSize ) || ( pxFile->ulFileSize == 0UL ) ) )
                {
                    if( pxFile->ulFileSize == 0UL )
                    {
                        xOriginalEntry.ulObjectCluster = 0;
                    }

                    xOriginalEntry.ulFileSize = pxFile->ulFileSize;
                    xError = FF_PutEntry( pxFile->pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry, NULL );
                }
            }
        }

        /* Handle Linked list! */
        FF_PendSemaphore( pxFile->pxIOManager->pvSemaphore );
        { /* Semaphore is required, or linked list could become corrupted. */
            pxFileChain = ( FF_FILE * ) pxFile->pxIOManager->FirstFile;

            if( pxFileChain == pxFile )
            {
                pxFile->pxIOManager->FirstFile = pxFile->pxNext;
            }
            else
            {
                while( pxFileChain )
                {
                    if( pxFileChain->pxNext == pxFile )
                    {
                        /* Found it, remove it from the list. */
                        pxFileChain->pxNext = pxFile->pxNext;
                        break;
                    }

                    pxFileChain = pxFileChain->pxNext;
                }
            }
        } /* Semaphore released, linked list was shortened! */
        FF_ReleaseSemaphore( pxFile->pxIOManager->pvSemaphore );

        #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 )
        {
            if( pxFile->pucBuffer != NULL )
            {
                /* Ensure any unaligned points are pushed to the disk! */
                if( pxFile->ucState & FF_BUFSTATE_WRITTEN )
                {
                    FF_Error_t xTempError;

                    xTempError = FF_BlockWrite( pxFile->pxIOManager, FF_FileLBA( pxFile ), 1, pxFile->pucBuffer, pdFALSE );

                    if( FF_isERR( xError ) == pdFALSE )
                    {
                        xError = xTempError;
                    }
                }

                ffconfigFREE( pxFile->pucBuffer );
            }
        }
        #endif /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */

        if( FF_isERR( xError ) == pdFALSE )
        {
            xError = FF_FlushCache( pxFile->pxIOManager ); /* Ensure all modified blocks are flushed to disk! */
        }

        ffconfigFREE( pxFile );
    }
    while( pdFALSE );

    return xError;
} /* FF_Close() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Make Filesize equal to the FilePointer and truncates the file to this position
 *
 *	@param	pxFile		FF_FILE object that was created by FF_Open().
 *
 *	@retval 0 on success.
 *	@retval negative if some error occurred
 *
 **/
FF_Error_t FF_SetEof( FF_FILE * pxFile )
{
    FF_Error_t xError;

    /* Check if the file was not deleted and if it was opened with write permissions: */
    if( ( ( pxFile->ulValidFlags & FF_VALID_FLAG_DELETED ) == 0 ) &&
        ( ( pxFile->ucMode & ( FF_MODE_WRITE | FF_MODE_APPEND | FF_MODE_CREATE ) ) != 0 ) )
    {
        pxFile->ulFileSize = pxFile->ulFilePointer;

        if( pxFile->ulObjectCluster != 0ul )
        {
            xError = FF_Truncate( pxFile, pdFALSE );
        }
        else
        {
            xError = FF_ERR_NONE;
        }
    }
    else
    {
        xError = FF_createERR( FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE, FF_SETEOF );
    }

    return xError;
} /* FF_SetEof() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Truncate a file to 'pxFile->ulFileSize'
 *
 *	@param	pxFile		FF_FILE object that was created by FF_Open().
 *
 *	@retval 0 on success.
 *	@retval negative if some error occurred
 *
 **/
static FF_Error_t FF_Truncate( FF_FILE * pxFile,
                               BaseType_t bClosing )
{
    FF_Error_t xError;
    FF_IOManager_t * pxIOManager = pxFile->pxIOManager;

    uint32_t ulClusterSize;
    uint32_t ulClusterCount;
    uint32_t ulClustersNeeded;

    /* The number of bytes contained in a cluster. */
    ulClusterSize = pxIOManager->xPartition.usBlkSize * pxIOManager->xPartition.ulSectorsPerCluster;

    /* See how many clusters have been allocated. */
    ulClusterCount = FF_GetChainLength( pxIOManager, pxFile->ulObjectCluster, NULL, &xError );

    /* Calculate the actual number of clusters needed, rounding up */
    ulClustersNeeded = ( pxFile->ulFileSize + ulClusterSize - 1 ) / ulClusterSize;

    if( bClosing != pdFALSE )
    {
        /* The handle will be closed after truncating.  This function is called
         * because Filesize is an exact multiple of ulClusterSize. */
        ulClustersNeeded = pxFile->ulFileSize / ulClusterSize;
    }
    else
    {
        /* This function is called to make the file size equal to the current
         * position within the file. Always keep an extra cluster to write to. */
        ulClustersNeeded = ( pxFile->ulFileSize + ulClusterSize ) / ulClusterSize;
    }

    /* First change the FAT chain. */
    if( ( FF_isERR( xError ) == pdFALSE ) && ( ulClusterCount > ulClustersNeeded ) )
    {
        if( ulClustersNeeded == 0ul )
        {
            FF_LockFAT( pxIOManager );
            {
                /* In FF_Truncate() */
                xError = FF_UnlinkClusterChain( pxIOManager, pxFile->ulObjectCluster, 0 );
            }
            FF_UnlockFAT( pxIOManager );

            if( FF_isERR( xError ) == pdFALSE )
            {
                FF_DirEnt_t xOriginalEntry;

                /* The directory denotes the address of the first data cluster of every file.
                 * Now change it to 'ulAddrCurrentCluster': */
                xError = FF_GetEntry( pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xOriginalEntry.ulObjectCluster = 0ul;
                    xError = FF_PutEntry( pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry, NULL );

                    if( FF_isERR( xError ) == pdFALSE )
                    {
                        pxFile->ulObjectCluster = 0ul;
                        pxFile->ulChainLength = 0ul;
                        pxFile->ulCurrentCluster = 0ul;
                        pxFile->ulEndOfChain = 0ul;
                    }
                }
            }
        }
        else
        {
            FF_LockFAT( pxIOManager );
            {
                uint32_t ulTruncateCluster = FF_TraverseFAT( pxIOManager, pxFile->ulObjectCluster, ulClustersNeeded - 1, &xError );

                if( FF_isERR( xError ) == pdFALSE )
                {
                    xError = FF_UnlinkClusterChain( pxIOManager, ulTruncateCluster, 1 );
                }
            }
            FF_UnlockFAT( pxIOManager );
        }
    }

    return xError;
} /* FF_Truncate() */
/*-----------------------------------------------------------*/
